feat(commands:rule|Mod): feat rule command
This commit is contained in:
parent
4e3fbd7f4d
commit
ad443c9c48
8 changed files with 252 additions and 35 deletions
|
@ -42,6 +42,8 @@
|
||||||
<w>pred</w>
|
<w>pred</w>
|
||||||
<w>pydig</w>
|
<w>pydig</w>
|
||||||
<w>pylint</w>
|
<w>pylint</w>
|
||||||
|
<w>regle</w>
|
||||||
|
<w>regles</w>
|
||||||
<w>releaselevel</w>
|
<w>releaselevel</w>
|
||||||
<w>rprint</w>
|
<w>rprint</w>
|
||||||
<w>skipcq</w>
|
<w>skipcq</w>
|
||||||
|
|
34
tuxbot/cogs/Mod/functions/converters.py
Normal file
34
tuxbot/cogs/Mod/functions/converters.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
from tuxbot.cogs.Mod.functions.exceptions import (
|
||||||
|
RuleTooLongException,
|
||||||
|
UnknownRuleException,
|
||||||
|
)
|
||||||
|
from tuxbot.cogs.Mod.models import Rule
|
||||||
|
|
||||||
|
|
||||||
|
def _(x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class RuleIDConverter(commands.Converter):
|
||||||
|
async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
|
||||||
|
arg = int(argument)
|
||||||
|
|
||||||
|
rule_row = await Rule.get_or_none(server_id=ctx.guild.id, rule_id=arg)
|
||||||
|
|
||||||
|
if not rule_row:
|
||||||
|
raise UnknownRuleException(_("Unknown rule"))
|
||||||
|
|
||||||
|
return arg
|
||||||
|
|
||||||
|
|
||||||
|
class RuleConverter(commands.Converter):
|
||||||
|
async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
|
||||||
|
if len(argument) > 300:
|
||||||
|
raise RuleTooLongException(
|
||||||
|
_("Rule length must be 300 characters or lower.")
|
||||||
|
)
|
||||||
|
|
||||||
|
return argument
|
13
tuxbot/cogs/Mod/functions/exceptions.py
Normal file
13
tuxbot/cogs/Mod/functions/exceptions.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
class ModException(commands.BadArgument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RuleTooLongException(ModException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownRuleException(ModException):
|
||||||
|
pass
|
|
@ -1,3 +1,4 @@
|
||||||
|
from tuxbot.cogs.Mod.models.rules import Rule
|
||||||
from tuxbot.core.config import set_for_key
|
from tuxbot.core.config import set_for_key
|
||||||
from tuxbot.core.config import Config
|
from tuxbot.core.config import Config
|
||||||
|
|
||||||
|
@ -5,7 +6,29 @@ from tuxbot.core.bot import Tux
|
||||||
from tuxbot.core.utils.functions.extra import ContextPlus
|
from tuxbot.core.utils.functions.extra import ContextPlus
|
||||||
|
|
||||||
|
|
||||||
async def save_lang(bot: Tux, ctx: ContextPlus, lang: str):
|
async def save_lang(bot: Tux, ctx: ContextPlus, lang: str) -> None:
|
||||||
set_for_key(
|
set_for_key(bot.config.Servers, ctx.guild.id, Config.Server, locale=lang)
|
||||||
bot.config.Servers, ctx.guild.id, Config.Server, locale=lang
|
|
||||||
)
|
|
||||||
|
async def get_server_rules(guild_id: int) -> list[Rule]:
|
||||||
|
return await Rule.filter(server_id=guild_id).all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_most_recent_server_rules(rules: list[Rule]) -> Rule:
|
||||||
|
return sorted(rules, key=lambda r: r.updated_at)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def paginate_server_rules(rules: list[Rule]) -> list[str]:
|
||||||
|
body = [""]
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
if len(body[-1] + format_rule(rule)) > 2000:
|
||||||
|
body.append(format_rule(rule) + "\n")
|
||||||
|
else:
|
||||||
|
body[-1] += format_rule(rule) + "\n"
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def format_rule(rule: Rule) -> str:
|
||||||
|
return f"**{rule.rule_id}** - {rule.content}"
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from tuxbot.cogs.Mod.functions.utils import save_lang
|
from tuxbot.cogs.Mod.functions.converters import RuleConverter, RuleIDConverter
|
||||||
|
from tuxbot.cogs.Mod.functions.exceptions import (
|
||||||
|
RuleTooLongException,
|
||||||
|
UnknownRuleException,
|
||||||
|
)
|
||||||
|
from tuxbot.cogs.Mod.functions.utils import (
|
||||||
|
save_lang,
|
||||||
|
get_server_rules,
|
||||||
|
format_rule,
|
||||||
|
get_most_recent_server_rules,
|
||||||
|
paginate_server_rules,
|
||||||
|
)
|
||||||
|
from tuxbot.cogs.Mod.models.rules import Rule
|
||||||
from tuxbot.core.utils import checks
|
from tuxbot.core.utils import checks
|
||||||
from tuxbot.core.bot import Tux
|
from tuxbot.core.bot import Tux
|
||||||
|
|
||||||
|
@ -26,6 +39,16 @@ class Mod(commands.Cog):
|
||||||
def __init__(self, bot: Tux):
|
def __init__(self, bot: Tux):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
|
async def cog_command_error(self, ctx: ContextPlus, error):
|
||||||
|
if isinstance(
|
||||||
|
error,
|
||||||
|
(
|
||||||
|
RuleTooLongException,
|
||||||
|
UnknownRuleException,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await ctx.send(_(str(error), ctx, self.bot.config))
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
|
@ -58,3 +81,123 @@ class Mod(commands.Cog):
|
||||||
)
|
)
|
||||||
|
|
||||||
await ctx.send(embed=e)
|
await ctx.send(embed=e)
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
@group_extra(
|
||||||
|
name="rule",
|
||||||
|
aliases=["rules", "regle", "regles"],
|
||||||
|
deletable=False,
|
||||||
|
invoke_without_command=True,
|
||||||
|
)
|
||||||
|
@commands.guild_only()
|
||||||
|
async def _rule(
|
||||||
|
self,
|
||||||
|
ctx: ContextPlus,
|
||||||
|
rule: RuleIDConverter,
|
||||||
|
members: commands.Greedy[discord.Member],
|
||||||
|
):
|
||||||
|
rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)
|
||||||
|
|
||||||
|
message = _(
|
||||||
|
"{}please read the following rule: \n{}", ctx, self.bot.config
|
||||||
|
)
|
||||||
|
authors = ""
|
||||||
|
|
||||||
|
for member in members:
|
||||||
|
if member in ctx.message.mentions:
|
||||||
|
authors += f"{member.name}#{member.discriminator}, "
|
||||||
|
else:
|
||||||
|
authors += f"{member.mention}, "
|
||||||
|
|
||||||
|
await ctx.send(message.format(authors, format_rule(rule_row)))
|
||||||
|
|
||||||
|
@_rule.command(name="list", aliases=["show", "all"])
|
||||||
|
async def _rule_list(self, ctx: ContextPlus):
|
||||||
|
rules = await get_server_rules(ctx.guild.id)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=_("Rules for {}", ctx, self.bot.config).format(
|
||||||
|
ctx.guild.name
|
||||||
|
),
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text=_("Latest change: {}", ctx, self.bot.config).format(
|
||||||
|
get_most_recent_server_rules(rules).created_at
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pages = paginate_server_rules(rules)
|
||||||
|
|
||||||
|
if len(pages) == 1:
|
||||||
|
embed.description = pages[0]
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
else:
|
||||||
|
for i, page in enumerate(pages):
|
||||||
|
embed.title = _(
|
||||||
|
"Rules for {} ({}/{})", ctx, self.bot.config
|
||||||
|
).format(ctx.guild.name, str(i + 1), str(len(pages)))
|
||||||
|
embed.description = page
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@checks.is_admin()
|
||||||
|
@_rule.command(name="add")
|
||||||
|
async def _rule_add(self, ctx: ContextPlus, *, rule: RuleConverter):
|
||||||
|
rule_row = await Rule()
|
||||||
|
rule_row.server_id = ctx.guild.id
|
||||||
|
rule_row.author_id = ctx.message.author.id
|
||||||
|
|
||||||
|
rule_row.rule_id = len(await get_server_rules(ctx.guild.id)) + 1 # type: ignore
|
||||||
|
rule_row.content = str(rule) # type: ignore
|
||||||
|
|
||||||
|
await rule_row.save()
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
_("Following rule added: \n{}", ctx, self.bot.config).format(
|
||||||
|
format_rule(rule_row)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@checks.is_admin()
|
||||||
|
@_rule.command(name="edit")
|
||||||
|
async def _rule_edit(
|
||||||
|
self,
|
||||||
|
ctx: ContextPlus,
|
||||||
|
rule: RuleIDConverter,
|
||||||
|
*,
|
||||||
|
content: RuleConverter,
|
||||||
|
):
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)
|
||||||
|
|
||||||
|
rule_row.content = str(content) # type: ignore
|
||||||
|
rule_row.updated_at = datetime.now() # type: ignore
|
||||||
|
|
||||||
|
await rule_row.save()
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
_("Following rule updated: \n{}", ctx, self.bot.config).format(
|
||||||
|
format_rule(rule_row)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@checks.is_admin()
|
||||||
|
@_rule.command(name="delete")
|
||||||
|
async def _rule_delete(
|
||||||
|
self,
|
||||||
|
ctx: ContextPlus,
|
||||||
|
rule: RuleIDConverter,
|
||||||
|
):
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)
|
||||||
|
|
||||||
|
await rule_row.delete()
|
||||||
|
|
||||||
|
await ctx.send(
|
||||||
|
_("Following rule deleted: \n{}", ctx, self.bot.config).format(
|
||||||
|
format_rule(rule_row)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
from .rules import *
|
||||||
from .warns import *
|
from .warns import *
|
||||||
|
|
28
tuxbot/cogs/Mod/models/rules.py
Normal file
28
tuxbot/cogs/Mod/models/rules.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import tortoise
|
||||||
|
from tortoise import fields
|
||||||
|
|
||||||
|
|
||||||
|
class Rule(tortoise.Model):
|
||||||
|
id = fields.BigIntField(pk=True)
|
||||||
|
server_id = fields.BigIntField()
|
||||||
|
author_id = fields.BigIntField()
|
||||||
|
rule_id = fields.IntField()
|
||||||
|
content = fields.TextField(max_length=300)
|
||||||
|
created_at = fields.DatetimeField(auto_now_add=True)
|
||||||
|
updated_at = fields.DatetimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table = "rules"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
f"<Rule id={self.id} "
|
||||||
|
f"server_id={self.server_id} "
|
||||||
|
f"author_id={self.author_id} "
|
||||||
|
f"rule_id={self.rule_id} "
|
||||||
|
f"content='{self.content}' "
|
||||||
|
f"created_at={self.created_at} "
|
||||||
|
f"updated_at={self.updated_at}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
__repr__ = __str__
|
|
@ -15,17 +15,7 @@ IP_REPLACEMENT = "■" * random.randint(3, 15)
|
||||||
class ContextPlus(commands.Context):
|
class ContextPlus(commands.Context):
|
||||||
# noinspection PyTypedDict
|
# noinspection PyTypedDict
|
||||||
async def send(
|
async def send(
|
||||||
self,
|
self, content=None, *, embed=None, deletable=True, **kwargs
|
||||||
content=None,
|
|
||||||
*,
|
|
||||||
tts=False,
|
|
||||||
embed=None,
|
|
||||||
file=None,
|
|
||||||
files=None,
|
|
||||||
delete_after=None,
|
|
||||||
nonce=None,
|
|
||||||
allowed_mentions=None,
|
|
||||||
deletable=True,
|
|
||||||
): # i know *args and **kwargs but, i prefer work with same values
|
): # i know *args and **kwargs but, i prefer work with same values
|
||||||
from tuxbot.core.utils.functions.utils import (
|
from tuxbot.core.utils.functions.utils import (
|
||||||
replace_in_dict,
|
replace_in_dict,
|
||||||
|
@ -45,7 +35,6 @@ class ContextPlus(commands.Context):
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(content) > 1800:
|
if len(content) > 1800:
|
||||||
file = discord.File(BytesIO(content.encode()), "output.txt")
|
|
||||||
content = "output too long..."
|
content = "output too long..."
|
||||||
if embed:
|
if embed:
|
||||||
e = embed.to_dict()
|
e = embed.to_dict()
|
||||||
|
@ -107,14 +96,7 @@ class ContextPlus(commands.Context):
|
||||||
hasattr(self.command, "deletable") and self.command.deletable
|
hasattr(self.command, "deletable") and self.command.deletable
|
||||||
) and deletable:
|
) and deletable:
|
||||||
message = await super().send(
|
message = await super().send(
|
||||||
content=content,
|
content=content, embed=embed, **kwargs
|
||||||
tts=tts,
|
|
||||||
embed=embed,
|
|
||||||
file=file,
|
|
||||||
files=files,
|
|
||||||
delete_after=delete_after,
|
|
||||||
nonce=nonce,
|
|
||||||
allowed_mentions=allowed_mentions,
|
|
||||||
)
|
)
|
||||||
await message.add_reaction("🗑")
|
await message.add_reaction("🗑")
|
||||||
|
|
||||||
|
@ -135,16 +117,7 @@ class ContextPlus(commands.Context):
|
||||||
await message.delete()
|
await message.delete()
|
||||||
return message
|
return message
|
||||||
|
|
||||||
return await super().send(
|
return await super().send(content=content, embed=embed, **kwargs)
|
||||||
content=content,
|
|
||||||
tts=tts,
|
|
||||||
embed=embed,
|
|
||||||
file=file,
|
|
||||||
files=files,
|
|
||||||
delete_after=delete_after,
|
|
||||||
nonce=nonce,
|
|
||||||
allowed_mentions=allowed_mentions,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self) -> aiohttp.ClientSession:
|
def session(self) -> aiohttp.ClientSession:
|
||||||
|
|
Loading…
Reference in a new issue