mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-20 18:06:08 -05:00
[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:
6
redbot/cogs/cleanup/__init__.py
Normal file
6
redbot/cogs/cleanup/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .cleanup import Cleanup
|
||||
from redbot.core.bot import Red
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Cleanup(bot))
|
||||
344
redbot/cogs/cleanup/cleanup.py
Normal file
344
redbot/cogs/cleanup/cleanup.py
Normal 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)
|
||||
17
redbot/cogs/cleanup/locales/messages.pot
Normal file
17
redbot/cogs/cleanup/locales/messages.pot
Normal 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"
|
||||
|
||||
|
||||
6
redbot/cogs/filter/__init__.py
Normal file
6
redbot/cogs/filter/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .filter import Filter
|
||||
from redbot.core.bot import Red
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Filter(bot))
|
||||
236
redbot/cogs/filter/filter.py
Normal file
236
redbot/cogs/filter/filter.py
Normal file
@@ -0,0 +1,236 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core import checks, Config, modlog
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core.utils.chat_formatting import pagify
|
||||
from redbot.core.utils.mod import is_mod_or_superior
|
||||
|
||||
_ = CogI18n("Filter", __file__)
|
||||
|
||||
|
||||
class Filter:
|
||||
"""Filter-related commands"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.settings = Config.get_conf(self, 4766951341)
|
||||
default_guild_settings = {
|
||||
"filter": [],
|
||||
"filterban_count": 0,
|
||||
"filterban_time": 0
|
||||
}
|
||||
default_member_settings = {
|
||||
"filter_count": 0,
|
||||
"next_reset_time": 0
|
||||
}
|
||||
self.settings.register_guild(**default_guild_settings)
|
||||
self.settings.register_member(**default_member_settings)
|
||||
self.bot.loop.create_task(
|
||||
modlog.register_casetype(
|
||||
"filterban", False, ":filing_cabinet: :hammer:",
|
||||
"Filter ban", "ban"
|
||||
)
|
||||
)
|
||||
|
||||
@commands.group(name="filter")
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_messages=True)
|
||||
async def _filter(self, ctx: commands.Context):
|
||||
"""Adds/removes words from filter
|
||||
|
||||
Use double quotes to add/remove sentences
|
||||
Using this command with no subcommands will send
|
||||
the list of the server's filtered words."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
server = ctx.guild
|
||||
author = ctx.author
|
||||
word_list = await self.settings.guild(server).filter()
|
||||
if word_list:
|
||||
words = ", ".join(word_list)
|
||||
words = _("Filtered in this server:") + "\n\n" + words
|
||||
try:
|
||||
for page in pagify(words, delims=[" ", "\n"], shorten_by=8):
|
||||
await author.send(page)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("I can't send direct messages to you."))
|
||||
|
||||
@_filter.command(name="add")
|
||||
async def filter_add(self, ctx: commands.Context, *, words: str):
|
||||
"""Adds words to the filter
|
||||
|
||||
Use double quotes to add sentences
|
||||
Examples:
|
||||
filter add word1 word2 word3
|
||||
filter add \"This is a sentence\""""
|
||||
server = ctx.guild
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
tmp = ""
|
||||
for word in split_words:
|
||||
if not word.startswith("\"")\
|
||||
and not word.endswith("\"") and not tmp:
|
||||
word_list.append(word)
|
||||
else:
|
||||
if word.startswith("\""):
|
||||
tmp += word[1:]
|
||||
elif word.endswith("\""):
|
||||
tmp += word[:-1]
|
||||
word_list.append(tmp)
|
||||
tmp = ""
|
||||
else:
|
||||
tmp += word
|
||||
added = await self.add_to_filter(server, word_list)
|
||||
if added:
|
||||
await ctx.send(_("Words added to filter."))
|
||||
else:
|
||||
await ctx.send(_("Words already in the filter."))
|
||||
|
||||
@_filter.command(name="remove")
|
||||
async def filter_remove(self, ctx: commands.Context, *, words: str):
|
||||
"""Remove words from the filter
|
||||
|
||||
Use double quotes to remove sentences
|
||||
Examples:
|
||||
filter remove word1 word2 word3
|
||||
filter remove \"This is a sentence\""""
|
||||
server = ctx.guild
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
tmp = ""
|
||||
for word in split_words:
|
||||
if not word.startswith("\"")\
|
||||
and not word.endswith("\"") and not tmp:
|
||||
word_list.append(word)
|
||||
else:
|
||||
if word.startswith("\""):
|
||||
tmp += word[1:]
|
||||
elif word.endswith("\""):
|
||||
tmp += word[:-1]
|
||||
word_list.append(tmp)
|
||||
tmp = ""
|
||||
else:
|
||||
tmp += word
|
||||
removed = await self.remove_from_filter(server, word_list)
|
||||
if removed:
|
||||
await ctx.send(_("Words removed from filter."))
|
||||
else:
|
||||
await ctx.send(_("Those words weren't in the filter."))
|
||||
|
||||
@_filter.command(name="ban")
|
||||
async def filter_ban(
|
||||
self, ctx: commands.Context, count: int, timeframe: int):
|
||||
"""
|
||||
Sets up an autoban if the specified number of messages are
|
||||
filtered in the specified amount of time (in seconds)
|
||||
"""
|
||||
if (count <= 0) != (timeframe <= 0):
|
||||
await ctx.send(
|
||||
_("Count and timeframe either both need to be 0 "
|
||||
"or both need to be greater than 0!"
|
||||
)
|
||||
)
|
||||
return
|
||||
elif count == 0 and timeframe == 0:
|
||||
await self.settings.guild(ctx.guild).filterban_count.set(0)
|
||||
await self.settings.guild(ctx.guild).filterban_time.set(0)
|
||||
await ctx.send(_("Autoban disabled."))
|
||||
else:
|
||||
await self.settings.guild(ctx.guild).filterban_count.set(count)
|
||||
await self.settings.guild(ctx.guild).filterban_time.set(timeframe)
|
||||
await ctx.send(_("Count and time have been set."))
|
||||
|
||||
async def add_to_filter(self, server: discord.Guild, words: list) -> bool:
|
||||
added = 0
|
||||
cur_list = await self.settings.guild(server).filter()
|
||||
for w in words:
|
||||
if w.lower() not in cur_list and w != "":
|
||||
cur_list.append(w.lower())
|
||||
added += 1
|
||||
if added:
|
||||
await self.settings.guild(server).filter.set(cur_list)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def remove_from_filter(self, server: discord.Guild, words: list) -> bool:
|
||||
removed = 0
|
||||
cur_list = await self.settings.guild(server).filter()
|
||||
for w in words:
|
||||
if w.lower() in cur_list:
|
||||
cur_list.remove(w.lower())
|
||||
removed += 1
|
||||
if removed:
|
||||
await self.settings.guild(server).filter.set(cur_list)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def check_filter(self, message: discord.Message):
|
||||
server = message.guild
|
||||
author = message.author
|
||||
word_list = await self.settings.guild(server).filter()
|
||||
filter_count = await self.settings.guild(server).filterban_count()
|
||||
filter_time = await self.settings.guild(server).filterban_time()
|
||||
user_count = await self.settings.member(author).filter_count()
|
||||
next_reset_time = await self.settings.member(author).next_reset_time()
|
||||
if filter_count > 0 and filter_time > 0:
|
||||
if message.created_at.timestamp() >= next_reset_time:
|
||||
next_reset_time = message.created_at.timestamp() + filter_time
|
||||
await self.settings.member(author).next_reset_time.set(
|
||||
next_reset_time
|
||||
)
|
||||
if user_count > 0:
|
||||
user_count = 0
|
||||
await self.settings.member(author).filter_count.set(user_count)
|
||||
|
||||
if word_list:
|
||||
for w in word_list:
|
||||
if w in message.content.lower():
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if filter_count > 0 and filter_time > 0:
|
||||
user_count += 1
|
||||
await self.settings.member(author).filter_count.set(user_count)
|
||||
if user_count >= filter_count and \
|
||||
message.created_at.timestamp() < next_reset_time:
|
||||
reason = "Autoban (too many filtered messages)"
|
||||
try:
|
||||
await server.ban(author, reason=reason)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
await modlog.create_case(
|
||||
server, message.created_at, "filterban",
|
||||
author, server.me, reason
|
||||
)
|
||||
|
||||
async def on_message(self, message: discord.Message):
|
||||
if isinstance(message.channel, discord.abc.PrivateChannel):
|
||||
return
|
||||
author = message.author
|
||||
valid_user = isinstance(author, discord.Member) and not author.bot
|
||||
|
||||
# Bots and mods or superior are ignored from the filter
|
||||
mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
|
||||
if not valid_user or mod_or_superior:
|
||||
return
|
||||
|
||||
await self.check_filter(message)
|
||||
|
||||
async def on_message_edit(self, _, message):
|
||||
author = message.author
|
||||
if message.guild is None or self.bot.user == author:
|
||||
return
|
||||
|
||||
valid_user = isinstance(author, discord.Member) and not author.bot
|
||||
mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
|
||||
if not valid_user or mod_or_superior:
|
||||
return
|
||||
|
||||
await self.check_filter(message)
|
||||
17
redbot/cogs/filter/locales/messages.pot
Normal file
17
redbot/cogs/filter/locales/messages.pot
Normal 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:33-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"
|
||||
|
||||
|
||||
6
redbot/cogs/mod/__init__.py
Normal file
6
redbot/cogs/mod/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from redbot.core.bot import Red
|
||||
from .mod import Mod
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(Mod(bot))
|
||||
58
redbot/cogs/mod/checks.py
Normal file
58
redbot/cogs/mod/checks.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
|
||||
|
||||
def mod_or_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
||||
# Author is bot owner or guild owner
|
||||
return True
|
||||
|
||||
admin_role = discord.utils.get(guild.roles, id=await ctx.bot.db.guild(guild).admin_role())
|
||||
mod_role = discord.utils.get(guild.roles, id=await ctx.bot.db.guild(guild).mod_role())
|
||||
|
||||
if admin_role in author.roles or mod_role in author.roles:
|
||||
return True
|
||||
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(author)
|
||||
good = all(getattr(resolved, name, None) == value for name, value in perms.items())
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
def admin_or_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if await ctx.bot.is_owner(author) or guild.owner == author:
|
||||
return True
|
||||
admin_role = discord.utils.get(guild.roles, id=await ctx.bot.db.guild(guild).admin_role())
|
||||
if admin_role in author.roles:
|
||||
return True
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(author)
|
||||
good = all(getattr(resolved, name, None) == value for name, value in perms.items())
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
def bot_has_voice_permissions(**perms):
|
||||
async def pred(ctx: commands.Context):
|
||||
guild = ctx.guild
|
||||
for vc in guild.voice_channels:
|
||||
resolved = vc.permissions_for(guild.me)
|
||||
good = all(getattr(resolved, name, None) == value for name, value in perms.items())
|
||||
if not good:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return commands.check(pred)
|
||||
17
redbot/cogs/mod/locales/messages.pot
Normal file
17
redbot/cogs/mod/locales/messages.pot
Normal 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:33-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"
|
||||
|
||||
|
||||
4
redbot/cogs/mod/log.py
Normal file
4
redbot/cogs/mod/log.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger("red.mod")
|
||||
1299
redbot/cogs/mod/mod.py
Normal file
1299
redbot/cogs/mod/mod.py
Normal file
File diff suppressed because it is too large
Load Diff
6
redbot/cogs/modlog/__init__.py
Normal file
6
redbot/cogs/modlog/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from redbot.core.bot import Red
|
||||
from .modlog import ModLog
|
||||
|
||||
|
||||
def setup(bot: Red):
|
||||
bot.add_cog(ModLog(bot))
|
||||
17
redbot/cogs/modlog/locales/messages.pot
Normal file
17
redbot/cogs/modlog/locales/messages.pot
Normal 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"
|
||||
|
||||
|
||||
154
redbot/cogs/modlog/modlog.py
Normal file
154
redbot/cogs/modlog/modlog.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from redbot.core import checks, modlog
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core.utils.chat_formatting import box
|
||||
|
||||
_ = CogI18n('ModLog', __file__)
|
||||
|
||||
|
||||
class ModLog:
|
||||
"""Log for mod actions"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
|
||||
@commands.group()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def modlogset(self, ctx: commands.Context):
|
||||
"""Settings for the mod log"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
|
||||
@modlogset.command()
|
||||
@commands.guild_only()
|
||||
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
||||
"""Sets a channel as mod log
|
||||
|
||||
Leaving the channel parameter empty will deactivate it"""
|
||||
guild = ctx.guild
|
||||
if channel:
|
||||
if channel.permissions_for(guild.me).send_messages:
|
||||
await modlog.set_modlog_channel(guild, channel)
|
||||
await ctx.send(
|
||||
_("Mod events will be sent to {}").format(
|
||||
channel.mention
|
||||
)
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_("I do not have permissions to "
|
||||
"send messages in {}!").format(channel.mention)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
await modlog.get_modlog_channel(guild)
|
||||
except RuntimeError:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
else:
|
||||
await modlog.set_modlog_channel(guild, None)
|
||||
await ctx.send(_("Mod log deactivated."))
|
||||
|
||||
@modlogset.command(name='cases')
|
||||
@commands.guild_only()
|
||||
async def set_cases(self, ctx: commands.Context, action: str = None):
|
||||
"""Enables or disables case creation for each type of mod action"""
|
||||
guild = ctx.guild
|
||||
|
||||
if action is None: # No args given
|
||||
casetypes = await modlog.get_all_casetypes()
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
title = _("Current settings:")
|
||||
msg = ""
|
||||
for ct in casetypes:
|
||||
enabled = await ct.is_enabled()
|
||||
value = 'enabled' if enabled else 'disabled'
|
||||
msg += '%s : %s\n' % (ct.name, value)
|
||||
|
||||
msg = title + "\n" + box(msg)
|
||||
await ctx.send(msg)
|
||||
return
|
||||
casetype = await modlog.get_casetype(action, guild)
|
||||
if not casetype:
|
||||
await ctx.send(_("That action is not registered"))
|
||||
else:
|
||||
|
||||
enabled = await casetype.is_enabled()
|
||||
await casetype.set_enabled(True if not enabled else False)
|
||||
|
||||
msg = (
|
||||
_('Case creation for {} actions is now {}.').format(
|
||||
action, 'enabled' if not enabled else 'disabled'
|
||||
)
|
||||
)
|
||||
await ctx.send(msg)
|
||||
|
||||
@modlogset.command()
|
||||
@commands.guild_only()
|
||||
async def resetcases(self, ctx: commands.Context):
|
||||
"""Resets modlog's cases"""
|
||||
guild = ctx.guild
|
||||
await modlog.reset_cases(guild)
|
||||
await ctx.send(_("Cases have been reset."))
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def case(self, ctx: commands.Context, number: int):
|
||||
"""Shows the specified case"""
|
||||
try:
|
||||
case = await modlog.get_case(number, ctx.guild, self.bot)
|
||||
except RuntimeError:
|
||||
await ctx.send(_("That case does not exist for that guild"))
|
||||
return
|
||||
else:
|
||||
await ctx.send(embed=await case.get_case_msg_content())
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def reason(self, ctx: commands.Context, case: int, *, reason: str = ""):
|
||||
"""Lets you specify a reason for mod-log's cases
|
||||
Please note that you can only edit cases you are
|
||||
the owner of unless you are a mod/admin or the guild owner"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if not reason:
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
return
|
||||
try:
|
||||
case_before = await modlog.get_case(case, guild, self.bot)
|
||||
except RuntimeError:
|
||||
await ctx.send(_("That case does not exist!"))
|
||||
return
|
||||
else:
|
||||
if case_before.moderator is None:
|
||||
# No mod set, so attempt to find out if the author
|
||||
# triggered the case creation with an action
|
||||
bot_perms = guild.me.guild_permissions
|
||||
if bot_perms.view_audit_log:
|
||||
case_type = await modlog.get_casetype(case_before.action_type, guild)
|
||||
audit_type = getattr(discord.AuditLogAction, case_type.audit_type)
|
||||
if audit_type:
|
||||
audit_case = None
|
||||
async for entry in guild.audit_logs(action=audit_type):
|
||||
if entry.target.id == case_before.user.id and \
|
||||
entry.user.id == case_before.moderator.id:
|
||||
audit_case = entry
|
||||
break
|
||||
if audit_case:
|
||||
case_before.moderator = audit_case.user
|
||||
is_guild_owner = author == guild.owner
|
||||
is_case_author = author == case_before.moderator
|
||||
author_is_mod = await ctx.bot.is_mod(author)
|
||||
if not (is_guild_owner or is_case_author or author_is_mod):
|
||||
await ctx.send(_("You are not authorized to modify that case!"))
|
||||
return
|
||||
to_modify = {
|
||||
"reason": reason,
|
||||
}
|
||||
if case_before.moderator != author:
|
||||
to_modify["amended_by"] = author
|
||||
to_modify["modified_at"] = ctx.message.created_at.timestamp()
|
||||
await case_before.edit(to_modify)
|
||||
await ctx.send(_("Reason has been updated."))
|
||||
Reference in New Issue
Block a user