1
Fork 0
mirror of https://github.com/wlinator/luminara.git synced 2024-10-02 18:03:12 +00:00

Merge pull request #23 from wlinator/moderation

Moderation: Kicks
This commit is contained in:
wlinator 2024-08-06 04:23:56 -04:00
commit bc0af82592
14 changed files with 135 additions and 189 deletions

View file

@ -109,8 +109,6 @@
"error_no_case_found_description": "no case found with that ID.",
"error_no_private_message_author": "Guild Only",
"error_no_private_message_description": "this command can only be used in servers.",
"error_not_allowed_in_channel_author": "Not Allowed In Channel",
"error_not_allowed_in_channel_description": "you can only use that command in {0}.",
"error_not_owner_author": "Owner Only",
"error_not_owner_description": "this command requires Lumi ownership permissions.",
"error_private_message_only_author": "Private Message Only",
@ -168,6 +166,9 @@
"mod_warn_dm": "**{0}** you have been warned in `{1}`.\n\n**Reason:** `{2}`",
"mod_warned_author": "User Warned",
"mod_warned_user": "user `{0}` has been warned.",
"mod_kicked_author": "User Kicked",
"mod_kicked_user": "user `{0}` has been kicked.",
"mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`",
"ping_author": "I'm online!",
"ping_footer": "Latency: {0}ms",
"ping_pong": "Pong!",
@ -189,5 +190,6 @@
"xp_lb_field_value": "level: **{0}**\nxp: `{1}/{2}`",
"xp_level": "Level {0}",
"xp_progress": "Progress to next level",
"xp_server_rank": "Server Rank: #{0}"
"xp_server_rank": "Server Rank: #{0}",
"error_command_not_found": "No command called \"{0}\" found."
}

View file

@ -66,13 +66,6 @@ async def on_command_error(ctx, error):
str(error),
)
elif isinstance(error, LumiExceptions.NotAllowedInChannel):
author_text = CONST.STRINGS["error_not_allowed_in_channel_author"]
description = CONST.STRINGS["error_not_allowed_in_channel_description"].format(
error.command_channel.mention,
)
ephemeral = True
else:
author_text = CONST.STRINGS["error_unknown_error_author"]
description = CONST.STRINGS["error_unknown_error_description"]
@ -111,8 +104,6 @@ class ErrorListener(Cog):
@Cog.listener()
async def on_command_error(self, ctx, error) -> None:
if isinstance(error, LumiExceptions.NotAllowedInChannel):
return
await on_command_error(ctx, error)
await self.log_command_error(ctx, error, ".")

View file

@ -17,25 +17,3 @@ def birthdays_enabled():
return True
return commands.check(predicate)
def allowed_in_channel():
async def predicate(ctx):
if ctx.guild is None:
return True
guild_config = GuildConfig(ctx.guild.id)
command_channel_id = guild_config.command_channel_id
if command_channel_id:
command_channel = await ctx.bot.get_or_fetch_channel(
ctx.guild,
command_channel_id,
)
if ctx.channel.id != command_channel_id and command_channel:
raise LumiExceptions.NotAllowedInChannel(command_channel)
return True
return commands.check(predicate)

View file

@ -12,7 +12,7 @@ class Constants:
TITLE = "Luminara"
AUTHOR = "wlinator"
LICENSE = "GNU General Public License v3.0"
VERSION = "2.8.4" # "Moderation: Config" update
VERSION = "2.8.5" # "Moderation: Kicks" update
# bot credentials
TOKEN: Optional[str] = os.environ.get("TOKEN", None)

View file

@ -21,14 +21,18 @@ class EmbedBuilder:
image_url=None,
thumbnail_url=None,
timestamp=None,
hide_author=False,
hide_author_icon=False,
):
if not author_text:
author_text = ctx.author.name
elif show_name:
description = f"**{ctx.author.name}** {description}"
if not hide_author:
if not author_text:
author_text = ctx.author.name
elif show_name:
description = f"**{ctx.author.name}** {description}"
if not hide_author_icon and not author_icon_url:
author_icon_url = ctx.author.display_avatar.url
if not author_icon_url:
author_icon_url = ctx.author.display_avatar.url
if not footer_text:
footer_text = "Luminara"
if not footer_icon_url:
@ -39,7 +43,12 @@ class EmbedBuilder:
description=description,
color=color or CONST.COLOR_DEFAULT,
)
embed.set_author(name=author_text, icon_url=author_icon_url, url=author_url)
if not hide_author:
embed.set_author(
name=author_text,
icon_url=None if hide_author_icon else author_icon_url,
url=author_url,
)
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
embed.timestamp = timestamp or datetime.datetime.now()
@ -63,6 +72,8 @@ class EmbedBuilder:
image_url=None,
thumbnail_url=None,
timestamp=None,
hide_author=False,
hide_author_icon=False,
):
return EmbedBuilder.create_embed(
ctx,
@ -78,6 +89,8 @@ class EmbedBuilder:
image_url=image_url,
thumbnail_url=thumbnail_url,
timestamp=timestamp,
hide_author=hide_author,
hide_author_icon=hide_author_icon,
)
@staticmethod
@ -93,6 +106,8 @@ class EmbedBuilder:
image_url=None,
thumbnail_url=None,
timestamp=None,
hide_author=False,
hide_author_icon=False,
):
return EmbedBuilder.create_embed(
ctx,
@ -108,6 +123,8 @@ class EmbedBuilder:
image_url=image_url,
thumbnail_url=thumbnail_url,
timestamp=timestamp,
hide_author=hide_author,
hide_author_icon=hide_author_icon,
)
@staticmethod
@ -123,6 +140,8 @@ class EmbedBuilder:
image_url=None,
thumbnail_url=None,
timestamp=None,
hide_author=False,
hide_author_icon=False,
):
return EmbedBuilder.create_embed(
ctx,
@ -138,6 +157,8 @@ class EmbedBuilder:
image_url=image_url,
thumbnail_url=thumbnail_url,
timestamp=timestamp,
hide_author=hide_author,
hide_author_icon=hide_author_icon,
)
@staticmethod
@ -153,6 +174,8 @@ class EmbedBuilder:
image_url=None,
thumbnail_url=None,
timestamp=None,
hide_author=False,
hide_author_icon=False,
):
return EmbedBuilder.create_embed(
ctx,
@ -168,4 +191,6 @@ class EmbedBuilder:
image_url=image_url,
thumbnail_url=thumbnail_url,
timestamp=timestamp,
hide_author=hide_author,
hide_author_icon=hide_author_icon,
)

View file

@ -1,6 +1,5 @@
import discord
from lib import formatter
from lib.constants import CONST
@ -11,64 +10,6 @@ def clean_error_embed(ctx):
)
class GenericErrors:
@staticmethod
def bad_arg(ctx, error):
embed = clean_error_embed(ctx)
if embed.description is None:
embed.description = formatter.shorten(str(error), 100)
else:
embed.description += formatter.shorten(str(error), 100)
embed.set_footer(
text=f"For more info do {formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}",
icon_url=CONST.QUESTION_ICON,
)
return embed
@staticmethod
def private_message_only(ctx):
embed = clean_error_embed(ctx)
if embed.description is None:
embed.description = "this command can only be used in private messages."
else:
embed.description += "this command can only be used in private messages."
embed.set_footer(
text=f"For more info do '{formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}'",
icon_url=CONST.QUESTION_ICON,
)
return embed
@staticmethod
def guild_only(ctx):
embed = clean_error_embed(ctx)
if embed.description is None:
embed.description = "this command can only be used in a server."
else:
embed.description += "this command can only be used in a server."
embed.set_footer(
text=f"For more info do '{formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}'",
icon_url=CONST.QUESTION_ICON,
)
return embed
@staticmethod
def channel_not_allowed(ctx, channel):
embed = clean_error_embed(ctx)
if embed.description is None:
embed.description = f"you can only do that command in {channel.mention}."
else:
embed.description += f"you can only do that command in {channel.mention}."
embed.set_footer(
text="This message will delete itself after 5s",
icon_url=CONST.EXCLAIM_ICON,
)
return embed
class EconErrors:
@staticmethod
def out_of_time(ctx):
@ -98,35 +39,3 @@ class EconErrors:
)
return embed
class MiscErrors:
@staticmethod
def prefix_too_long(ctx):
embed = clean_error_embed(ctx)
if embed.description is None:
embed.description = "this prefix is too long."
else:
embed.description += "this prefix is too long."
embed.set_footer(
text=f"For more info do '{formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}'",
icon_url=CONST.QUESTION_ICON,
)
return embed
class HelpErrors:
@staticmethod
def error_message(ctx, error):
"""
See discord.ext.commands.HelpCommand.send_error_message
"""
embed = clean_error_embed(ctx)
embed.description += error
embed.set_footer(
text=f"See '{formatter.get_prefix(ctx)}help'",
icon_url=CONST.EXCLAIM_ICON,
)
return embed

View file

@ -1,15 +1,6 @@
from discord.ext import commands
class NotAllowedInChannel(commands.CheckFailure):
"""
Raised when checks.allowed_in_channel() fails.
"""
def __init__(self, commands_channel):
self.command_channel = commands_channel
class BirthdaysDisabled(commands.CheckFailure):
"""
Raised when the birthdays module is disabled in ctx.guild.

View file

@ -1,7 +1,5 @@
import discord
from discord.ext import bridge, commands
from lib import checks
from modules.economy import balance, blackjack, daily, give, slots
@ -18,7 +16,6 @@ class Economy(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
@commands.cooldown(1, 10, commands.BucketType.user)
async def balance_command(self, ctx):
return await balance.cmd(ctx)
@ -31,7 +28,6 @@ class Economy(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
async def blackjack_command(self, ctx, *, bet: int):
return await blackjack.cmd(ctx, bet)
@ -43,7 +39,6 @@ class Economy(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
async def daily_command(self, ctx):
return await daily.cmd(ctx)
@ -53,7 +48,6 @@ class Economy(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
async def give_command(self, ctx, *, user: discord.Member, amount: int):
return await give.cmd(ctx, user, amount)
@ -62,7 +56,6 @@ class Economy(commands.Cog):
help="Give a server member some cash. You can use ID or mention them.",
)
@commands.guild_only()
@checks.allowed_in_channel()
async def give_command_prefixed(self, ctx, user: discord.User, *, amount: int):
try:
member = await ctx.guild.fetch_member(user.id)
@ -79,7 +72,6 @@ class Economy(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
@commands.cooldown(1, 5, commands.BucketType.user)
async def slots_command(self, ctx, *, bet: int):
return await slots.cmd(self, ctx, bet)

View file

@ -8,7 +8,7 @@ from loguru import logger
from config.parser import JsonCache
from lib import interaction
from lib.embeds.error import EconErrors, GenericErrors
from lib.embeds.error import EconErrors
from services.currency_service import Currency
from services.stats_service import BlackJackStats
@ -187,7 +187,7 @@ async def cmd(ctx, bet: int):
stats.push()
except Exception as e:
await ctx.respond(embed=GenericErrors.default_exception(ctx))
# await ctx.respond(embed=GenericErrors.default_exception(ctx))
logger.error("Something went wrong in the blackjack command: ", e)
finally:

View file

@ -1,6 +1,5 @@
from discord.ext import bridge, commands
from lib import checks
from modules.levels import leaderboard, level
@ -16,7 +15,6 @@ class Levels(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
@commands.cooldown(1, 30, commands.BucketType.user)
async def level_command(self, ctx) -> None:
await level.rank(ctx)
@ -29,7 +27,6 @@ class Levels(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
@commands.cooldown(1, 180, commands.BucketType.user)
async def leaderboard_command(self, ctx) -> None:
await leaderboard.cmd(ctx)

View file

@ -5,7 +5,6 @@ from discord.commands import SlashCommandGroup
from discord.ext import bridge, commands, tasks
from Client import LumiBot
from lib import checks
from modules.config import c_prefix
from modules.misc import avatar, backup, info, introduction, invite, ping, xkcd
@ -28,7 +27,6 @@ class Misc(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
async def avatar(self, ctx, user: discord.Member) -> None:
return await avatar.get_avatar(ctx, user)
@ -38,7 +36,6 @@ class Misc(commands.Cog):
description="Simple status check.",
help="Simple status check.",
)
@checks.allowed_in_channel()
async def ping(self, ctx) -> None:
await ping.ping(self, ctx)
@ -47,7 +44,6 @@ class Misc(commands.Cog):
description="See Lumi's uptime since the last update.",
help="See how long Lumi has been online since his last update.",
)
@checks.allowed_in_channel()
async def uptime(self, ctx) -> None:
await ping.uptime(self, ctx, self.start_time)
@ -56,7 +52,6 @@ class Misc(commands.Cog):
description="Generate an invite link.",
help="Generate a link to invite Lumi to your own server!",
)
@checks.allowed_in_channel()
async def invite_command(self, ctx) -> None:
await invite.cmd(ctx)
@ -67,7 +62,6 @@ class Misc(commands.Cog):
guild_only=True,
)
@commands.guild_only()
@checks.allowed_in_channel()
async def prefix_command(self, ctx) -> None:
await c_prefix.get_prefix(ctx)
@ -77,7 +71,6 @@ class Misc(commands.Cog):
description="Shows basic Lumi stats.",
help="Shows basic Lumi stats.",
)
@checks.allowed_in_channel()
async def info_command(self, ctx) -> None:
unix_timestamp: int = int(round(self.start_time.timestamp()))
await info.cmd(self, ctx, unix_timestamp)

View file

@ -1,7 +1,7 @@
import discord
from discord.ext import bridge, commands
from modules.moderation import ban, cases, warn, timeout
from modules.moderation import ban, cases, warn, timeout, kick
class Moderation(commands.Cog):
@ -163,6 +163,25 @@ class Moderation(commands.Cog):
):
await timeout.untimeout_user(ctx, target, reason)
@bridge.bridge_command(
name="kick",
aliases=["k"],
description="Kick a user from the server.",
help="Kicks a user from the server.",
guild_only=True,
)
@bridge.has_permissions(kick_members=True)
@commands.bot_has_permissions(kick_members=True)
@commands.guild_only()
async def kick_command(
self,
ctx,
target: discord.Member,
*,
reason: str | None = None,
):
await kick.kick_user(self, ctx, target, reason)
def setup(client):
client.add_cog(Moderation(client))

View file

@ -0,0 +1,57 @@
import asyncio
import discord
from lib import formatter
from lib.constants import CONST
from lib.embed_builder import EmbedBuilder
from modules.moderation.utils.actionable import async_actionable
from modules.moderation.utils.case_handler import create_case
from typing import Optional
from discord.ext.commands import UserConverter
async def kick_user(cog, ctx, target: discord.Member, reason: Optional[str] = None):
bot_member = await cog.client.get_or_fetch_member(ctx.guild, ctx.bot.user.id)
await async_actionable(target, ctx.author, bot_member)
output_reason = reason or CONST.STRINGS["mod_no_reason"]
try:
await target.send(
embed=EmbedBuilder.create_warning_embed(
ctx,
author_text=CONST.STRINGS["mod_kicked_author"],
description=CONST.STRINGS["mod_kick_dm"].format(
target.name,
ctx.guild.name,
output_reason,
),
show_name=False,
),
)
dm_sent = True
except (discord.HTTPException, discord.Forbidden):
dm_sent = False
await target.kick(
reason=CONST.STRINGS["mod_reason"].format(
ctx.author.name,
formatter.shorten(output_reason, 200),
),
)
respond_task = ctx.respond(
embed=EmbedBuilder.create_success_embed(
ctx,
author_text=CONST.STRINGS["mod_kicked_author"],
description=CONST.STRINGS["mod_kicked_user"].format(target.name),
footer_text=CONST.STRINGS["mod_dm_sent"]
if dm_sent
else CONST.STRINGS["mod_dm_not_sent"],
),
)
target_user = await UserConverter().convert(ctx, str(target.id))
create_case_task = create_case(ctx, target_user, "KICK", reason)
await asyncio.gather(respond_task, create_case_task, return_exceptions=True)

View file

@ -1,10 +1,9 @@
import discord
from discord.ext import commands
from config.parser import JsonCache
from lib.embeds.error import HelpErrors
art = JsonCache.read_json("art")
from lib.embed_builder import EmbedBuilder
from lib.constants import CONST
from lib.exceptions.LumiExceptions import LumiException
class LumiHelp(commands.HelpCommand):
@ -22,10 +21,11 @@ class LumiHelp(commands.HelpCommand):
return "`{}{}`".format(self.context.clean_prefix, command.qualified_name)
async def send_bot_help(self, mapping):
embed = discord.Embed(color=discord.Color.blurple())
embed.set_author(name="Help Command", icon_url=art["logo"]["transparent"])
embed.description = "Full list of commands: https://wiki.wlinator.org/cmdlist"
embed = EmbedBuilder.create_info_embed(
ctx=self.context,
author_text="Help Command",
show_name=False,
)
for cog, lumi_commands in mapping.items():
filtered = await self.filter_commands(lumi_commands, sort=True)
@ -46,10 +46,11 @@ class LumiHelp(commands.HelpCommand):
await channel.send(embed=embed)
async def send_command_help(self, command):
embed = discord.Embed(
title=f"{self.context.clean_prefix}{command.qualified_name}",
color=discord.Color.blurple(),
embed = EmbedBuilder.create_success_embed(
ctx=self.context,
author_text=f"{self.context.clean_prefix}{command.qualified_name}",
description=command.help,
show_name=False,
)
usage_value = "`{}{} {}`".format(
@ -69,25 +70,16 @@ class LumiHelp(commands.HelpCommand):
await channel.send(embed=embed)
async def send_error_message(self, error):
channel = self.get_destination()
await channel.send(embed=HelpErrors.error_message(self.context, error))
raise LumiException(error)
async def send_group_help(self, group):
channel = self.get_destination()
await channel.send(
embed=HelpErrors.error_message(
self.context,
f'No command called "{group.qualified_name}" found.',
),
raise LumiException(
CONST.STRINGS["error_command_not_found"].format(group.qualified_name),
)
async def send_cog_help(self, cog):
channel = self.get_destination()
await channel.send(
embed=HelpErrors.error_message(
self.context,
f'No command called "{cog.qualified_name}" found.',
),
raise LumiException(
CONST.STRINGS["error_command_not_found"].format(cog.qualified_name),
)
async def command_callback(self, ctx, *, command=None):
@ -103,7 +95,7 @@ class LumiHelp(commands.HelpCommand):
if cog is not None:
return await self.send_cog_help(cog)
maybe_coro = discord.utils.maybe_coroutine
maybe_coro = discord.utils.maybe_coroutine # type: ignore
# If it's not a cog then it's a command.
# Since we want to have detailed errors when someone