mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-21 18:27:59 -05:00
[V3] Warning system (#1173)
* [V3 Warning] initial work on a warning system for v3 * [V3 Warning] rename some stuff and add a case type * [V3 Warnings] rename package from warning * [V3 Warnings] restructuring commands * [V3 Warnings] remove expiry stuff + other refactoring * [V3 Warnings] refactoring action logic * [V3 Warnings] rewrites to action logic * [V3 Warnings] add regen_messages.py
This commit is contained in:
371
redbot/cogs/warnings/warnings.py
Normal file
371
redbot/cogs/warnings/warnings.py
Normal file
@@ -0,0 +1,371 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
import asyncio
|
||||
|
||||
from redbot.cogs.warnings.helpers import warning_points_add_check, get_command_for_exceeded_points, \
|
||||
get_command_for_dropping_points, warning_points_remove_check
|
||||
from redbot.core import Config, modlog, checks
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.context import RedContext
|
||||
from redbot.core.i18n import CogI18n
|
||||
from redbot.core.utils.mod import is_admin_or_superior
|
||||
from redbot.core.utils.chat_formatting import warning, pagify
|
||||
|
||||
_ = CogI18n("Warnings", __file__)
|
||||
|
||||
|
||||
class Warnings:
|
||||
"""A warning system for Red"""
|
||||
|
||||
default_guild = {
|
||||
"actions": [],
|
||||
"reasons": {},
|
||||
"allow_custom_reasons": False
|
||||
}
|
||||
|
||||
default_member = {
|
||||
"total_points": 0,
|
||||
"status": "",
|
||||
"warnings": {}
|
||||
}
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.config = Config.get_conf(self, identifier=5757575755)
|
||||
self.config.register_guild(**self.default_guild)
|
||||
self.config.register_member(**self.default_member)
|
||||
self.bot = bot
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self.register_warningtype())
|
||||
|
||||
@staticmethod
|
||||
async def register_warningtype():
|
||||
try:
|
||||
await modlog.register_casetype(
|
||||
"warning", True, "\N{WARNING SIGN}", "Warning", None
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def warningset(self, ctx: RedContext):
|
||||
"""Warning settings"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@warningset.command()
|
||||
@commands.guild_only()
|
||||
async def allowcustomreasons(self, ctx: RedContext, allowed: bool):
|
||||
"""Allow or disallow custom reasons for a warning"""
|
||||
guild = ctx.guild
|
||||
await self.config.guild(guild).allow_custom_reasons.set(allowed)
|
||||
await ctx.send(
|
||||
_("Custom reasons have been {}").format(_("enabled") if allowed else _("disabled"))
|
||||
)
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def warnaction(self, ctx: RedContext):
|
||||
"""Action management"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@warnaction.command(name="add")
|
||||
@commands.guild_only()
|
||||
async def action_add(self, ctx: RedContext, name: str, points: int):
|
||||
"""Create an action to be taken at a specified point count
|
||||
Duplicate action names are not allowed"""
|
||||
guild = ctx.guild
|
||||
|
||||
await ctx.send("Would you like to enter commands to be run? (y/n)")
|
||||
|
||||
def same_author_check(m):
|
||||
return m.author == ctx.author
|
||||
|
||||
try:
|
||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send(_("Ok then"))
|
||||
return
|
||||
|
||||
if msg.content.lower() == "y":
|
||||
exceed_command = await get_command_for_exceeded_points(ctx)
|
||||
if exceed_command is None:
|
||||
return
|
||||
drop_command = await get_command_for_dropping_points(ctx)
|
||||
if drop_command is None:
|
||||
return
|
||||
else:
|
||||
exceed_command = None
|
||||
drop_command = None
|
||||
to_add = {
|
||||
"action_name": name,
|
||||
"points": points,
|
||||
"exceed_command": exceed_command,
|
||||
"drop_command": drop_command
|
||||
}
|
||||
|
||||
# Have all details for the action, now save the action
|
||||
guild_settings = self.config.guild(guild)
|
||||
async with guild_settings.actions() as registered_actions:
|
||||
for act in registered_actions:
|
||||
if act["action_name"] == to_add["action_name"]:
|
||||
await ctx.send(_("Duplicate action name found!"))
|
||||
break
|
||||
else:
|
||||
registered_actions.append(to_add)
|
||||
# Sort in descending order by point count for ease in
|
||||
# finding the highest possible action to take
|
||||
registered_actions.sort(key=lambda a: a["point_count"], reverse=True)
|
||||
await ctx.tick()
|
||||
|
||||
@warnaction.command(name="del")
|
||||
@commands.guild_only()
|
||||
async def action_del(self, ctx: RedContext, action_name: str):
|
||||
"""Delete the point count action with the specified name"""
|
||||
guild = ctx.guild
|
||||
guild_settings = self.config.guild(guild)
|
||||
async with guild_settings.actions() as registered_actions:
|
||||
to_remove = None
|
||||
for act in registered_actions:
|
||||
if act["action_name"] == action_name:
|
||||
to_remove = act
|
||||
break
|
||||
if to_remove:
|
||||
registered_actions.remove(to_remove)
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def warnreason(self, ctx: RedContext):
|
||||
"""Add reasons for warnings"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@warnreason.command(name="add")
|
||||
@commands.guild_only()
|
||||
async def reason_add(self, ctx: RedContext, name: str, points: int, *, description: str):
|
||||
"""Add a reason to be available for warnings"""
|
||||
guild = ctx.guild
|
||||
|
||||
if name.lower() == "custom":
|
||||
await ctx.send("That cannot be used as a reason name!")
|
||||
return
|
||||
to_add = {
|
||||
"points": points,
|
||||
"description": description
|
||||
}
|
||||
completed = {
|
||||
name.lower(): to_add
|
||||
}
|
||||
|
||||
guild_settings = self.config.guild(guild)
|
||||
|
||||
async with guild_settings.reasons() as registered_reasons:
|
||||
registered_reasons.update(completed)
|
||||
|
||||
await ctx.send(_("That reason has been registered"))
|
||||
|
||||
@warnreason.command(name="del")
|
||||
@commands.guild_only()
|
||||
async def reason_del(self, ctx: RedContext, reason_name: str):
|
||||
"""Delete the reason with the specified name"""
|
||||
guild = ctx.guild
|
||||
guild_settings = self.config.guild(guild)
|
||||
async with guild_settings.reasons() as registered_reasons:
|
||||
if registered_reasons.pop(reason_name.lower(), None):
|
||||
await ctx.send(_("Removed reason {}").format(reason_name))
|
||||
else:
|
||||
await ctx.send(_("That is not a registered reason name"))
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def reasonlist(self, ctx: RedContext):
|
||||
"""List all configured reasons for warnings"""
|
||||
guild = ctx.guild
|
||||
guild_settings = self.config.guild(guild)
|
||||
msg_list = []
|
||||
async with guild_settings.reasons() as registered_reasons:
|
||||
for r in registered_reasons.keys():
|
||||
msg_list.append(
|
||||
"Name: {}\nPoints: {}\nAction: {}".format(
|
||||
r, r["points"], r["action"]
|
||||
)
|
||||
)
|
||||
await ctx.send_interactive(msg_list)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def actionlist(self, ctx: RedContext):
|
||||
"""List the actions to be taken at specific point values"""
|
||||
guild = ctx.guild
|
||||
guild_settings = self.config.guild(guild)
|
||||
msg_list = []
|
||||
async with guild_settings.actions() as registered_actions:
|
||||
for r in registered_actions.keys():
|
||||
msg_list.append(
|
||||
"Name: {}\nPoints: {}\nDescription: {}".format(
|
||||
r, r["points"], r["description"]
|
||||
)
|
||||
)
|
||||
await ctx.send_interactive(msg_list)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def warn(self, ctx: RedContext, user: discord.Member, reason: str):
|
||||
"""Warn the user for the specified reason
|
||||
Reason must be a registered reason, or custom if custom reasons are allowed"""
|
||||
reason_type = {}
|
||||
if reason.lower() == "custom":
|
||||
custom_allowed = await self.config.guild(ctx.guild).allow_custom_reasons()
|
||||
if not custom_allowed:
|
||||
await ctx.send(
|
||||
_(
|
||||
"Custom reasons are not allowed! Please see {} for "
|
||||
"a complete list of valid reasons"
|
||||
).format(
|
||||
"`{}reasonlist`".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
return
|
||||
reason_type = await self.custom_warning_reason(ctx)
|
||||
else:
|
||||
guild_settings = self.config.guild(ctx.guild)
|
||||
async with guild_settings.reasons() as registered_reasons:
|
||||
if reason.lower() not in registered_reasons:
|
||||
await ctx.send(_("That is not a registered reason!"))
|
||||
else:
|
||||
reason_type = registered_reasons[reason.lower()]
|
||||
|
||||
member_settings = self.config.member(user)
|
||||
current_point_count = await member_settings.total_points()
|
||||
warning_to_add = {
|
||||
str(ctx.message.id): {
|
||||
"points": reason_type["points"],
|
||||
"description": reason_type["description"],
|
||||
"mod": ctx.author.id
|
||||
}
|
||||
}
|
||||
async with member_settings.warnings() as user_warnings:
|
||||
user_warnings.update(warning_to_add)
|
||||
current_point_count += reason_type["points"]
|
||||
await member_settings.total_points.set(current_point_count)
|
||||
|
||||
await warning_points_add_check(self.config, ctx, user, current_point_count)
|
||||
await ctx.tick()
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def warnings(self, ctx: RedContext, userid: int=None):
|
||||
"""Show warnings for the specified user.
|
||||
If userid is None, show warnings for the person running the command
|
||||
Note that showing warnings for users other than yourself requires
|
||||
appropriate permissions"""
|
||||
if userid is None:
|
||||
user = ctx.author
|
||||
else:
|
||||
if not is_admin_or_superior(self.bot, ctx.author):
|
||||
await ctx.send(
|
||||
warning(
|
||||
_("You are not allowed to check "
|
||||
"warnings for other users!")
|
||||
)
|
||||
)
|
||||
return
|
||||
else:
|
||||
user = ctx.guild.get_member(userid)
|
||||
if user is None: # user not in guild
|
||||
user = namedtuple("Member", "id guild")(userid, ctx.guild)
|
||||
msg = ""
|
||||
member_settings = self.config.member(user)
|
||||
async with member_settings.warnings() as user_warnings:
|
||||
if not user_warnings.keys(): # no warnings for the user
|
||||
await ctx.send(_("That user has no warnings!"))
|
||||
else:
|
||||
for key in user_warnings.keys():
|
||||
mod = ctx.guild.get_member(user_warnings[key]["mod"])
|
||||
if mod is None:
|
||||
mod = discord.utils.get(
|
||||
self.bot.get_all_members(),
|
||||
id=user_warnings[key]["mod"]
|
||||
)
|
||||
if mod is None:
|
||||
mod = await self.bot.get_user_info(
|
||||
user_warnings[key]["mod"]
|
||||
)
|
||||
msg += "{} point warning {} issued by {} for {}\n".format(
|
||||
user_warnings[key]["points"],
|
||||
key,
|
||||
mod,
|
||||
user_warnings[key]["description"]
|
||||
)
|
||||
await ctx.send_interactive(
|
||||
pagify(msg), box_lang="Warnings for {}".format(user)
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def unwarn(self, ctx: RedContext, user_id: int, warn_id: str):
|
||||
"""Removes the specified warning from the user specified"""
|
||||
guild = ctx.guild
|
||||
member = guild.get_member(user_id)
|
||||
if member is None: # no longer in guild, but need a "member" object
|
||||
member = namedtuple("Member", "guild id")(guild, user_id)
|
||||
member_settings = self.config.member(member)
|
||||
|
||||
current_point_count = await member_settings.total_points()
|
||||
await warning_points_remove_check(self.config, ctx, member, current_point_count)
|
||||
async with member_settings.warnings() as user_warnings:
|
||||
if warn_id not in user_warnings.keys():
|
||||
await ctx.send("That warning doesn't exist!")
|
||||
return
|
||||
else:
|
||||
current_point_count -= user_warnings[warn_id]["points"]
|
||||
await member_settings.total_points.set(current_point_count)
|
||||
user_warnings.pop(warn_id)
|
||||
await ctx.tick()
|
||||
|
||||
@staticmethod
|
||||
async def custom_warning_reason(ctx: RedContext):
|
||||
"""Handles getting description and points for custom reasons"""
|
||||
to_add = {
|
||||
"points": 0,
|
||||
"description": ""
|
||||
}
|
||||
|
||||
def same_author_check(m):
|
||||
return m.author == ctx.author
|
||||
|
||||
await ctx.send(_("How many points should be given for this reason?"))
|
||||
try:
|
||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send(_("Ok then"))
|
||||
return
|
||||
try:
|
||||
int(msg.content)
|
||||
except ValueError:
|
||||
await ctx.send(_("That isn't a number!"))
|
||||
return
|
||||
else:
|
||||
if int(msg.content) <= 0:
|
||||
await ctx.send(_("The point value needs to be greater than 0!"))
|
||||
return
|
||||
to_add["points"] = int(msg.content)
|
||||
|
||||
await ctx.send(_("Enter a description for this reason"))
|
||||
try:
|
||||
msg = await ctx.bot.wait_for("message", check=same_author_check, timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send(_("Ok then"))
|
||||
return
|
||||
to_add["description"] = msg.content
|
||||
return to_add
|
||||
Reference in New Issue
Block a user