Fix common issues in cogs (ephemeral, opt_type, check)

This commit is contained in:
Zeva Rose 2022-02-04 09:53:06 -07:00
parent fa4785f073
commit 88d596cad7
22 changed files with 310 additions and 396 deletions

View file

@ -12,6 +12,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Ban, Unban from jarvis.db.models import Ban, Unban
from jarvis.utils import build_embed, find from jarvis.utils import build_embed, find
@ -90,16 +91,14 @@ class BanCog(CacheCog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
@slash_command(name="ban", description="Ban a user") @slash_command(name="ban", description="Ban a user")
@slash_option(name="user", description="User to ban", opt_type=OptionTypes.USER, required=True)
@slash_option( @slash_option(
name="user", description="User to ban", option_type=OptionTypes.USER, required=True name="reason", description="Ban reason", opt_type=OptionTypes.STRING, required=True
)
@slash_option(
name="reason", description="Ban reason", option_type=OptionTypes.STRING, required=True
) )
@slash_option( @slash_option(
name="btype", name="btype",
description="Ban type", description="Ban type",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
choices=[ choices=[
SlashCommandChoice(name="Permanent", value="perm"), SlashCommandChoice(name="Permanent", value="perm"),
@ -107,7 +106,7 @@ class BanCog(CacheCog):
SlashCommandChoice(name="Soft", value="soft"), SlashCommandChoice(name="Soft", value="soft"),
], ],
) )
@admin_or_permissions(Permissions.BAN_MEMBERS) @check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _ban( async def _ban(
self, self,
ctx: InteractionContext, ctx: InteractionContext,
@ -117,19 +116,19 @@ class BanCog(CacheCog):
duration: int = 4, duration: int = 4,
) -> None: ) -> None:
if not user or user == ctx.author: if not user or user == ctx.author:
await ctx.send("You cannot ban yourself.", hidden=True) await ctx.send("You cannot ban yourself.", ephemeral=True)
return return
if user == self.bot.user: if user == self.bot.user:
await ctx.send("I'm afraid I can't let you do that", hidden=True) await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
return return
if btype == "temp" and duration < 0: if btype == "temp" and duration < 0:
await ctx.send("You cannot set a temp ban to < 0 hours.", hidden=True) await ctx.send("You cannot set a temp ban to < 0 hours.", ephemeral=True)
return return
elif btype == "temp" and duration > 744: elif btype == "temp" and duration > 744:
await ctx.send("You cannot set a temp ban to > 1 month", hidden=True) await ctx.send("You cannot set a temp ban to > 1 month", ephemeral=True)
return return
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", ephemeral=True)
return return
await ctx.defer() await ctx.defer()
@ -167,7 +166,7 @@ class BanCog(CacheCog):
try: try:
await ctx.guild.ban(user, reason=reason) await ctx.guild.ban(user, reason=reason)
except Exception as e: except Exception as e:
await ctx.send(f"Failed to ban user:\n```\n{e}\n```", hidden=True) await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True)
return return
send_failed = False send_failed = False
if mtype == "soft": if mtype == "soft":
@ -184,12 +183,12 @@ class BanCog(CacheCog):
@slash_command(name="unban", description="Unban a user") @slash_command(name="unban", description="Unban a user")
@slash_option( @slash_option(
name="user", description="User to unban", option_type=OptionTypes.STRING, required=True name="user", description="User to unban", opt_type=OptionTypes.STRING, required=True
) )
@slash_option( @slash_option(
name="reason", description="Unban reason", option_type=OptionTypes.STRING, required=True name="reason", description="Unban reason", opt_type=OptionTypes.STRING, required=True
) )
@admin_or_permissions(Permissions.BAN_MEMBERS) @check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _unban( async def _unban(
self, self,
ctx: InteractionContext, ctx: InteractionContext,
@ -197,7 +196,7 @@ class BanCog(CacheCog):
reason: str, reason: str,
) -> None: ) -> None:
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", ephemeral=True)
return return
orig_user = user orig_user = user
@ -255,7 +254,7 @@ class BanCog(CacheCog):
database_ban_info = Ban.objects(**search).first() database_ban_info = Ban.objects(**search).first()
if not discord_ban_info and not database_ban_info: if not discord_ban_info and not database_ban_info:
await ctx.send(f"Unable to find user {orig_user}", hidden=True) await ctx.send(f"Unable to find user {orig_user}", ephemeral=True)
elif discord_ban_info: elif discord_ban_info:
await self.discord_apply_unban(ctx, discord_ban_info.user, reason) await self.discord_apply_unban(ctx, discord_ban_info.user, reason)
@ -284,7 +283,7 @@ class BanCog(CacheCog):
@slash_option( @slash_option(
name="btype", name="btype",
description="Ban type", description="Ban type",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="All", value=0), SlashCommandChoice(name="All", value=0),
@ -296,19 +295,19 @@ class BanCog(CacheCog):
@slash_option( @slash_option(
name="active", name="active",
description="Active bans", description="Active bans",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)],
) )
@admin_or_permissions(Permissions.BAN_MEMBERS) @check(admin_or_permissions(Permissions.BAN_MEMBERS))
async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None: async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None:
active = bool(active) active = bool(active)
exists = self.check_cache(ctx, type=type, active=active) exists = self.check_cache(ctx, type=type, active=active)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
types = [0, "perm", "temp", "soft"] types = [0, "perm", "temp", "soft"]

View file

@ -7,6 +7,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Kick from jarvis.db.models import Kick
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -17,22 +18,20 @@ class KickCog(Scale):
"""J.A.R.V.I.S. KickCog.""" """J.A.R.V.I.S. KickCog."""
@slash_command(name="kick", description="Kick a user") @slash_command(name="kick", description="Kick a user")
@slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True)
@slash_option( @slash_option(
name="user", description="User to kick", option_type=OptionTypes.USER, required=True name="reason", description="Kick reason", opt_type=OptionTypes.STRING, required=True
) )
@slash_option( @check(admin_or_permissions(Permissions.BAN_MEMBERS))
name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True
)
@admin_or_permissions(Permissions.BAN_MEMBERS)
async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None: async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None:
if not user or user == ctx.author: if not user or user == ctx.author:
await ctx.send("You cannot kick yourself.", hidden=True) await ctx.send("You cannot kick yourself.", ephemeral=True)
return return
if user == self.bot.user: if user == self.bot.user:
await ctx.send("I'm afraid I can't let you do that", hidden=True) await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
return return
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", ephemeral=True)
return return
guild_name = ctx.guild.name guild_name = ctx.guild.name
embed = build_embed( embed = build_embed(

View file

@ -10,6 +10,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Mute from jarvis.db.models import Mute
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -23,25 +24,23 @@ class MuteCog(Scale):
self.bot = bot self.bot = bot
@slash_command(name="mute", description="Mute a user") @slash_command(name="mute", description="Mute a user")
@slash_option( @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True)
name="user", description="User to mute", option_type=OptionTypes.USER, required=True
)
@slash_option( @slash_option(
name="reason", name="reason",
description="Reason for mute", description="Reason for mute",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@slash_option( @slash_option(
name="time", name="time",
description="Duration of mute, default 1", description="Duration of mute, default 1",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@slash_option( @slash_option(
name="scale", name="scale",
description="Time scale, default Hour(s)", description="Time scale, default Hour(s)",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Minute(s)", value=1), SlashCommandChoice(name="Minute(s)", value=1),
@ -50,26 +49,28 @@ class MuteCog(Scale):
SlashCommandChoice(name="Week(s)", value=604800), SlashCommandChoice(name="Week(s)", value=604800),
], ],
) )
@admin_or_permissions( @check(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
) )
async def _timeout( async def _timeout(
self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60
) -> None: ) -> None:
if user == ctx.author: if user == ctx.author:
await ctx.send("You cannot mute yourself.", hidden=True) await ctx.send("You cannot mute yourself.", ephemeral=True)
return return
if user == self.bot.user: if user == self.bot.user:
await ctx.send("I'm afraid I can't let you do that", hidden=True) await ctx.send("I'm afraid I can't let you do that", ephemeral=True)
return return
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", ephemeral=True)
return return
# Max 4 weeks (2419200 seconds) per API # Max 4 weeks (2419200 seconds) per API
duration = time * scale duration = time * scale
if duration > 2419200: if duration > 2419200:
await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", hidden=True) await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True)
return return
await user.timeout(communication_disabled_until=duration, reason=reason) await user.timeout(communication_disabled_until=duration, reason=reason)
@ -97,17 +98,19 @@ class MuteCog(Scale):
@slash_command(name="unmute", description="Unmute a user") @slash_command(name="unmute", description="Unmute a user")
@slash_option( @slash_option(
name="user", description="User to unmute", option_type=OptionTypes.USER, required=True name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True
) )
@admin_or_permissions( @check(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS admin_or_permissions(
Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS
)
) )
async def _unmute(self, ctx: InteractionContext, user: Member) -> None: async def _unmute(self, ctx: InteractionContext, user: Member) -> None:
if ( if (
not user.communication_disabled_until not user.communication_disabled_until
or user.communication_disabled_until < datetime.now() # noqa: W503 or user.communication_disabled_until < datetime.now() # noqa: W503
): ):
await ctx.send("User is not muted", hidden=True) await ctx.send("User is not muted", ephemeral=True)
return return
embed = build_embed( embed = build_embed(

View file

@ -6,6 +6,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Autopurge, Purge from jarvis.db.models import Autopurge, Purge
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
@ -21,13 +22,13 @@ class PurgeCog(Scale):
@slash_option( @slash_option(
name="amount", name="amount",
description="Amount of messages to purge, default 10", description="Amount of messages to purge, default 10",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@admin_or_permissions(Permissions.MANAGE_MESSAGES) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None: async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None:
if amount < 1: if amount < 1:
await ctx.send("Amount must be >= 1", hidden=True) await ctx.send("Amount must be >= 1", ephemeral=True)
return return
await ctx.defer() await ctx.defer()
channel = ctx.channel channel = ctx.channel
@ -48,31 +49,31 @@ class PurgeCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Channel to autopurge", description="Channel to autopurge",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@slash_option( @slash_option(
name="delay", name="delay",
description="Seconds to keep message before purge, default 30", description="Seconds to keep message before purge, default 30",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@admin_or_permissions(Permissions.MANAGE_MESSAGES) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
async def _autopurge_add( async def _autopurge_add(
self, ctx: InteractionContext, channel: GuildText, delay: int = 30 self, ctx: InteractionContext, channel: GuildText, delay: int = 30
) -> None: ) -> None:
if not isinstance(channel, GuildText): if not isinstance(channel, GuildText):
await ctx.send("Channel must be a GuildText channel", hidden=True) await ctx.send("Channel must be a GuildText channel", ephemeral=True)
return return
if delay <= 0: if delay <= 0:
await ctx.send("Delay must be > 0", hidden=True) await ctx.send("Delay must be > 0", ephemeral=True)
return return
elif delay > 300: elif delay > 300:
await ctx.send("Delay must be < 5 minutes", hidden=True) await ctx.send("Delay must be < 5 minutes", ephemeral=True)
return return
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first() autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first()
if autopurge: if autopurge:
await ctx.send("Autopurge already exists.", hidden=True) await ctx.send("Autopurge already exists.", ephemeral=True)
return return
_ = Autopurge( _ = Autopurge(
guild=ctx.guild.id, guild=ctx.guild.id,
@ -88,14 +89,14 @@ class PurgeCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Channel to remove from autopurge", description="Channel to remove from autopurge",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_MESSAGES) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None: async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None:
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
if not autopurge: if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True) await ctx.send("Autopurge does not exist.", ephemeral=True)
return return
autopurge.delete() autopurge.delete()
await ctx.send(f"Autopurge removed from {channel.mention}.") await ctx.send(f"Autopurge removed from {channel.mention}.")
@ -108,22 +109,22 @@ class PurgeCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Channel to update", description="Channel to update",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@slash_option( @slash_option(
name="delay", name="delay",
description="New time to save", description="New time to save",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_MESSAGES) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES))
async def _autopurge_update( async def _autopurge_update(
self, ctx: InteractionContext, channel: GuildText, delay: int self, ctx: InteractionContext, channel: GuildText, delay: int
) -> None: ) -> None:
autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id)
if not autopurge: if not autopurge:
await ctx.send("Autopurge does not exist.", hidden=True) await ctx.send("Autopurge does not exist.", ephemeral=True)
return return
autopurge.delay = delay autopurge.delay = delay
autopurge.save() autopurge.save()

View file

@ -11,6 +11,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Roleping from jarvis.db.models import Roleping
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -27,14 +28,12 @@ class RolepingCog(CacheCog):
@slash_command( @slash_command(
name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping"
) )
@slash_option( @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
name="role", description="Role to add", option_type=OptionTypes.ROLE, required=True @check(admin_or_permissions(Permissions.MANAGE_GUILD))
)
@admin_or_permissions(Permissions.MANAGE_GUILD)
async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None: async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first()
if roleping: if roleping:
await ctx.send(f"Role `{role.name}` already in roleping.", hidden=True) await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True)
return return
_ = Roleping( _ = Roleping(
role=role.id, role=role.id,
@ -47,13 +46,13 @@ class RolepingCog(CacheCog):
@slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role")
@slash_option( @slash_option(
name="role", description="Role to remove", option_type=OptionTypes.ROLE, required=True name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None: async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=role.id) roleping = Roleping.objects(guild=ctx.guild.id, role=role.id)
if not roleping: if not roleping:
await ctx.send("Roleping does not exist", hidden=True) await ctx.send("Roleping does not exist", ephemeral=True)
return return
roleping.delete() roleping.delete()
@ -63,16 +62,16 @@ class RolepingCog(CacheCog):
async def _roleping_list(self, ctx: InteractionContext) -> None: async def _roleping_list(self, ctx: InteractionContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
rolepings = Roleping.objects(guild=ctx.guild.id) rolepings = Roleping.objects(guild=ctx.guild.id)
if not rolepings: if not rolepings:
await ctx.send("No rolepings configured", hidden=True) await ctx.send("No rolepings configured", ephemeral=True)
return return
embeds = [] embeds = []
@ -137,29 +136,27 @@ class RolepingCog(CacheCog):
sub_cmd_name="user", sub_cmd_name="user",
sub_cmd_description="Add a user as a bypass to a roleping", sub_cmd_description="Add a user as a bypass to a roleping",
) )
@slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True)
@slash_option( @slash_option(
name="user", description="User to add", option_type=OptionTypes.USER, required=True name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
) )
@slash_option( @check(admin_or_permissions(Permissions.MANAGE_GUILD))
name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True
)
@admin_or_permissions(Permissions.MANAGE_GUILD)
async def _roleping_bypass_user( async def _roleping_bypass_user(
self, ctx: InteractionContext, user: Member, rping: Role self, ctx: InteractionContext, user: Member, rping: Role
) -> None: ) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
return return
if user.id in roleping.bypass["users"]: if user.id in roleping.bypass["users"]:
await ctx.send(f"{user.mention} already in bypass", hidden=True) await ctx.send(f"{user.mention} already in bypass", ephemeral=True)
return return
if len(roleping.bypass["users"]) == 10: if len(roleping.bypass["users"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 users in bypass. Please consider using roles for roleping bypass", "Already have 10 users in bypass. Please consider using roles for roleping bypass",
hidden=True, ephemeral=True,
) )
return return
@ -168,7 +165,7 @@ class RolepingCog(CacheCog):
if matching_role: if matching_role:
await ctx.send( await ctx.send(
f"{user.mention} already has bypass via {matching_role[0].mention}", f"{user.mention} already has bypass via {matching_role[0].mention}",
hidden=True, ephemeral=True,
) )
return return
@ -182,28 +179,26 @@ class RolepingCog(CacheCog):
sub_cmd_name="role", sub_cmd_name="role",
description="Add a role as a bypass to roleping", description="Add a role as a bypass to roleping",
) )
@slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
@slash_option( @slash_option(
name="role", description="Role to add", option_type=OptionTypes.ROLE, required=True name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
) )
@slash_option( @check(admin_or_permissions(Permissions.MANAGE_GUILD))
name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True
)
@admin_or_permissions(Permissions.MANAGE_GUILD)
async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
return return
if role.id in roleping.bypass["roles"]: if role.id in roleping.bypass["roles"]:
await ctx.send(f"{role.mention} already in bypass", hidden=True) await ctx.send(f"{role.mention} already in bypass", ephemeral=True)
return return
if len(roleping.bypass["roles"]) == 10: if len(roleping.bypass["roles"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 roles in bypass. " "Already have 10 roles in bypass. "
"Please consider consolidating roles for roleping bypass", "Please consider consolidating roles for roleping bypass",
hidden=True, ephemeral=True,
) )
return return
@ -220,22 +215,22 @@ class RolepingCog(CacheCog):
sub_cmd_description="Remove a bypass from a roleping (restoring it)", sub_cmd_description="Remove a bypass from a roleping (restoring it)",
) )
@slash_option( @slash_option(
name="user", description="User to remove", option_type=OptionTypes.USER, required=True name="user", description="User to remove", opt_type=OptionTypes.USER, required=True
) )
@slash_option( @slash_option(
name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _roleping_restore_user( async def _roleping_restore_user(
self, ctx: InteractionContext, user: Member, rping: Role self, ctx: InteractionContext, user: Member, rping: Role
) -> None: ) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
return return
if user.id not in roleping.bypass["users"]: if user.id not in roleping.bypass["users"]:
await ctx.send(f"{user.mention} not in bypass", hidden=True) await ctx.send(f"{user.mention} not in bypass", ephemeral=True)
return return
roleping.bypass["users"].delete(user.id) roleping.bypass["users"].delete(user.id)
@ -249,29 +244,29 @@ class RolepingCog(CacheCog):
description="Remove a bypass from a roleping (restoring it)", description="Remove a bypass from a roleping (restoring it)",
) )
@slash_option( @slash_option(
name="role", description="Role to remove", option_type=OptionTypes.ROLE, required=True name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True
) )
@slash_option( @slash_option(
name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True
) )
@admin_or_permissions(manage_guild=True) @check(admin_or_permissions(manage_guild=True))
async def _roleping_restore_role( async def _roleping_restore_role(
self, ctx: InteractionContext, role: Role, rping: Role self, ctx: InteractionContext, role: Role, rping: Role
) -> None: ) -> None:
roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first()
if not roleping: if not roleping:
await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True)
return return
if role.id in roleping.bypass["roles"]: if role.id in roleping.bypass["roles"]:
await ctx.send(f"{role.mention} already in bypass", hidden=True) await ctx.send(f"{role.mention} already in bypass", ephemeral=True)
return return
if len(roleping.bypass["roles"]) == 10: if len(roleping.bypass["roles"]) == 10:
await ctx.send( await ctx.send(
"Already have 10 roles in bypass. " "Already have 10 roles in bypass. "
"Please consider consolidating roles for roleping bypass", "Please consider consolidating roles for roleping bypass",
hidden=True, ephemeral=True,
) )
return return

View file

@ -10,6 +10,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Warning from jarvis.db.models import Warning
from jarvis.utils import build_embed from jarvis.utils import build_embed
@ -25,33 +26,31 @@ class WarningCog(CacheCog):
super().__init__(bot) super().__init__(bot)
@slash_command(name="warn", description="Warn a user") @slash_command(name="warn", description="Warn a user")
@slash_option( @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True)
name="user", description="User to warn", option_type=OptionTypes.USER, required=True
)
@slash_option( @slash_option(
name="reason", name="reason",
description="Reason for warning", description="Reason for warning",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@slash_option( @slash_option(
name="duration", name="duration",
description="Duration of warning in hours, default 24", description="Duration of warning in hours, default 24",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _warn( async def _warn(
self, ctx: InteractionContext, user: User, reason: str, duration: int = 24 self, ctx: InteractionContext, user: User, reason: str, duration: int = 24
) -> None: ) -> None:
if len(reason) > 100: if len(reason) > 100:
await ctx.send("Reason must be < 100 characters", hidden=True) await ctx.send("Reason must be < 100 characters", ephemeral=True)
return return
if duration <= 0: if duration <= 0:
await ctx.send("Duration must be > 0", hidden=True) await ctx.send("Duration must be > 0", ephemeral=True)
return return
elif duration >= 120: elif duration >= 120:
await ctx.send("Duration must be < 5 days", hidden=True) await ctx.send("Duration must be < 5 days", ephemeral=True)
return return
await ctx.defer() await ctx.defer()
_ = Warning( _ = Warning(
@ -77,28 +76,26 @@ class WarningCog(CacheCog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
@slash_command(name="warnings", description="Get count of user warnings") @slash_command(name="warnings", description="Get count of user warnings")
@slash_option( @slash_option(name="user", description="User to view", opt_type=OptionTypes.USER, required=True)
name="user", description="User to view", option_type=OptionTypes.USER, required=True
)
@slash_option( @slash_option(
name="active", name="active",
description="View active only", description="View active only",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="Yes", value=1),
SlashCommandChoice(name="No", value=0), SlashCommandChoice(name="No", value=0),
], ],
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None:
active = bool(active) active = bool(active)
exists = self.check_cache(ctx, user_id=user.id, active=active) exists = self.check_cache(ctx, user_id=user.id, active=active)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
warnings = Warning.objects( warnings = Warning.objects(

View file

@ -9,6 +9,7 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.data.unicode import emoji_list from jarvis.data.unicode import emoji_list
from jarvis.db.models import Autoreact from jarvis.db.models import Autoreact
@ -70,13 +71,13 @@ class AutoReactCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Autoreact channel to add emote to", description="Autoreact channel to add emote to",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@slash_option( @slash_option(
name="emote", description="Emote to add", option_type=OptionTypes.STRING, required=True name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None: async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None:
await ctx.defer() await ctx.defer()
custom_emoji = self.custom_emote.match(emote) custom_emoji = self.custom_emote.match(emote)
@ -84,13 +85,13 @@ class AutoReactCog(Scale):
if not custom_emoji and not standard_emoji: if not custom_emoji and not standard_emoji:
await ctx.send( await ctx.send(
"Please use either an emote from this server or a unicode emoji.", "Please use either an emote from this server or a unicode emoji.",
hidden=True, ephemeral=True,
) )
return return
if custom_emoji: if custom_emoji:
emoji_id = int(custom_emoji.group(1)) emoji_id = int(custom_emoji.group(1))
if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): if not find(lambda x: x.id == emoji_id, ctx.guild.emojis):
await ctx.send("Please use a custom emote from this server.", hidden=True) await ctx.send("Please use a custom emote from this server.", ephemeral=True)
return return
autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first()
if not autoreact: if not autoreact:
@ -99,13 +100,13 @@ class AutoReactCog(Scale):
if emote in autoreact.reactions: if emote in autoreact.reactions:
await ctx.send( await ctx.send(
f"Emote already added to {channel.mention} autoreactions.", f"Emote already added to {channel.mention} autoreactions.",
hidden=True, ephemeral=True,
) )
return return
if len(autoreact.reactions) >= 5: if len(autoreact.reactions) >= 5:
await ctx.send( await ctx.send(
"Max number of reactions hit. Remove a different one to add this one", "Max number of reactions hit. Remove a different one to add this one",
hidden=True, ephemeral=True,
) )
return return
autoreact.reactions.append(emote) autoreact.reactions.append(emote)
@ -120,16 +121,16 @@ class AutoReactCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Autoreact channel to remove emote from", description="Autoreact channel to remove emote from",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@slash_option( @slash_option(
name="emote", name="emote",
description="Emote to remove (use all to delete)", description="Emote to remove (use all to delete)",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _autoreact_remove( async def _autoreact_remove(
self, ctx: InteractionContext, channel: GuildText, emote: str self, ctx: InteractionContext, channel: GuildText, emote: str
) -> None: ) -> None:
@ -137,7 +138,7 @@ class AutoReactCog(Scale):
if not autoreact: if not autoreact:
await ctx.send( await ctx.send(
f"Please create autoreact first with /autoreact add {channel.mention} {emote}", f"Please create autoreact first with /autoreact add {channel.mention} {emote}",
hidden=True, ephemeral=True,
) )
return return
if emote.lower() == "all": if emote.lower() == "all":
@ -146,7 +147,7 @@ class AutoReactCog(Scale):
elif emote not in autoreact.reactions: elif emote not in autoreact.reactions:
await ctx.send( await ctx.send(
f"{emote} not used in {channel.mention} autoreactions.", f"{emote} not used in {channel.mention} autoreactions.",
hidden=True, ephemeral=True,
) )
return return
else: else:
@ -164,7 +165,7 @@ class AutoReactCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Autoreact channel to list", description="Autoreact channel to list",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None: async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None:
@ -172,7 +173,7 @@ class AutoReactCog(Scale):
if not exists: if not exists:
await ctx.send( await ctx.send(
f"Please create autoreact first with /autoreact add {channel.mention} <emote>", f"Please create autoreact first with /autoreact add {channel.mention} <emote>",
hidden=True, ephemeral=True,
) )
return return
message = "" message = ""
@ -187,4 +188,4 @@ class AutoReactCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add AutoReactCog to J.A.R.V.I.S.""" """Add AutoReactCog to J.A.R.V.I.S."""
bot.add_cog(AutoReactCog(bot)) AutoReactCog(bot)

View file

@ -56,7 +56,7 @@ class CTCCog(CacheCog):
"Listen here, dipshit. Don't be like <@256110768724901889>. " "Listen here, dipshit. Don't be like <@256110768724901889>. "
"Make your guesses < 800 characters." "Make your guesses < 800 characters."
), ),
hidden=True, ephemeral=True,
) )
return return
elif not valid.fullmatch(guess): elif not valid.fullmatch(guess):
@ -65,18 +65,18 @@ class CTCCog(CacheCog):
"Listen here, dipshit. Don't be like <@256110768724901889>. " "Listen here, dipshit. Don't be like <@256110768724901889>. "
"Make your guesses *readable*." "Make your guesses *readable*."
), ),
hidden=True, ephemeral=True,
) )
return return
elif invites.search(guess): elif invites.search(guess):
await ctx.send( await ctx.send(
"Listen here, dipshit. No using this to bypass sending invite links.", "Listen here, dipshit. No using this to bypass sending invite links.",
hidden=True, ephemeral=True,
) )
return return
guessed = Guess.objects(guess=guess).first() guessed = Guess.objects(guess=guess).first()
if guessed: if guessed:
await ctx.send("Already guessed, dipshit.", hidden=True) await ctx.send("Already guessed, dipshit.", ephemeral=True)
return return
result = await self._session.post(self.url, data=guess) result = await self._session.post(self.url, data=guess)
correct = False correct = False
@ -84,7 +84,7 @@ class CTCCog(CacheCog):
await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!") await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!")
correct = True correct = True
else: else:
await ctx.send("Nope.", hidden=True) await ctx.send("Nope.", ephemeral=True)
_ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save()
@slash_command( @slash_command(
@ -97,10 +97,10 @@ class CTCCog(CacheCog):
async def _guesses(self, ctx: InteractionContext) -> None: async def _guesses(self, ctx: InteractionContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
@ -153,4 +153,4 @@ class CTCCog(CacheCog):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add CTCCog to J.A.R.V.I.S.""" """Add CTCCog to J.A.R.V.I.S."""
bot.add_cog(CTCCog(bot)) CTCCog(bot)

View file

@ -156,7 +156,7 @@ class DbrandCog(Scale):
@slash_option( @slash_option(
name="search", name="search",
description="Country search query (2 character code, country name, flag emoji)", description="Country search query (2 character code, country name, flag emoji)",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@cooldown(bucket=Buckets.USER, rate=1, interval=2) @cooldown(bucket=Buckets.USER, rate=1, interval=2)
@ -266,4 +266,4 @@ class DbrandCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add dbrandcog to J.A.R.V.I.S.""" """Add dbrandcog to J.A.R.V.I.S."""
bot.add_cog(DbrandCog(bot)) DbrandCog(bot)

View file

@ -68,14 +68,14 @@ class DevCog(Scale):
@slash_option( @slash_option(
name="method", name="method",
description="Hash method", description="Hash method",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes], choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes],
) )
@slash_option( @slash_option(
name="data", name="data",
description="Data to hash", description="Data to hash",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@cooldown(bucket=Buckets.USER, rate=1, interval=2) @cooldown(bucket=Buckets.USER, rate=1, interval=2)
@ -83,7 +83,7 @@ class DevCog(Scale):
if not data: if not data:
await ctx.send( await ctx.send(
"No data to hash", "No data to hash",
hidden=True, ephemeral=True,
) )
return return
text = True text = True
@ -105,20 +105,20 @@ class DevCog(Scale):
@slash_option( @slash_option(
name="version", name="version",
description="UUID version", description="UUID version",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]], choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]],
) )
@slash_option( @slash_option(
name="data", name="data",
description="Data for UUID version 3,5", description="Data for UUID version 3,5",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=False, required=False,
) )
async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None: async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None:
version = int(version) version = int(version)
if version in [3, 5] and not data: if version in [3, 5] and not data:
await ctx.send(f"UUID{version} requires data.", hidden=True) await ctx.send(f"UUID{version} requires data.", ephemeral=True)
return return
if version == 4: if version == 4:
await ctx.send(f"UUID4: `{uuidpy.uuid4()}`") await ctx.send(f"UUID4: `{uuidpy.uuid4()}`")
@ -180,14 +180,14 @@ class DevCog(Scale):
@slash_option( @slash_option(
name="method", name="method",
description="Encode method", description="Encode method",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
) )
@slash_option( @slash_option(
name="data", name="data",
description="Data to encode", description="Data to encode",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None:
@ -205,14 +205,14 @@ class DevCog(Scale):
@slash_option( @slash_option(
name="method", name="method",
description="Decode method", description="Decode method",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods],
) )
@slash_option( @slash_option(
name="data", name="data",
description="Data to encode", description="Data to encode",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None: async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None:
@ -222,7 +222,7 @@ class DevCog(Scale):
if invites.search(decoded): if invites.search(decoded):
await ctx.send( await ctx.send(
"Please don't use this to bypass invite restrictions", "Please don't use this to bypass invite restrictions",
hidden=True, ephemeral=True,
) )
return return
fields = [ fields = [
@ -243,4 +243,4 @@ class DevCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add DevCog to J.A.R.V.I.S.""" """Add DevCog to J.A.R.V.I.S."""
bot.add_cog(DevCog(bot)) DevCog(bot)

View file

@ -33,19 +33,19 @@ class ErrorHandlerCog(commands.Cog):
if isinstance(error, commands.errors.MissingPermissions) or isinstance( if isinstance(error, commands.errors.MissingPermissions) or isinstance(
error, commands.errors.CheckFailure error, commands.errors.CheckFailure
): ):
await ctx.send("I'm afraid I can't let you do that.", hidden=True) await ctx.send("I'm afraid I can't let you do that.", ephemeral=True)
elif isinstance(error, commands.errors.CommandNotFound): elif isinstance(error, commands.errors.CommandNotFound):
return return
elif isinstance(error, commands.errors.CommandOnCooldown): elif isinstance(error, commands.errors.CommandOnCooldown):
await ctx.send( await ctx.send(
"Command on cooldown. " "Command on cooldown. "
f"Please wait {error.retry_after:0.2f}s before trying again", f"Please wait {error.retry_after:0.2f}s before trying again",
hidden=True, ephemeral=True,
) )
else: else:
await ctx.send( await ctx.send(
f"Error processing command:\n```{error}```", f"Error processing command:\n```{error}```",
hidden=True, ephemeral=True,
) )
raise error raise error
slash.commands[ctx.command].reset_cooldown(ctx) slash.commands[ctx.command].reset_cooldown(ctx)
@ -53,4 +53,4 @@ class ErrorHandlerCog(commands.Cog):
def setup(bot: commands.Bot) -> None: def setup(bot: commands.Bot) -> None:
"""Add ErrorHandlerCog to J.A.R.V.I.S.""" """Add ErrorHandlerCog to J.A.R.V.I.S."""
bot.add_cog(ErrorHandlerCog(bot)) ErrorHandlerCog(bot)

View file

@ -32,12 +32,12 @@ class GitlabCog(CacheCog):
@slash_command( @slash_command(
name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids
) )
@slash_option(name="id", description="Issue ID", option_type=OptionTypes.INTEGER, required=True) @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True)
async def _issue(self, ctx: InteractionContext, id: int) -> None: async def _issue(self, ctx: InteractionContext, id: int) -> None:
try: try:
issue = self.project.issues.get(int(id)) issue = self.project.issues.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
await ctx.send("Issue does not exist.", hidden=True) await ctx.send("Issue does not exist.", ephemeral=True)
return return
assignee = issue.assignee assignee = issue.assignee
if assignee: if assignee:
@ -101,13 +101,13 @@ class GitlabCog(CacheCog):
scopes=guild_ids, scopes=guild_ids,
) )
@slash_option( @slash_option(
name="id", description="Milestone ID", option_type=OptionTypes.INTEGER, required=True name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True
) )
async def _milestone(self, ctx: InteractionContext, id: int) -> None: async def _milestone(self, ctx: InteractionContext, id: int) -> None:
try: try:
milestone = self.project.milestones.get(int(id)) milestone = self.project.milestones.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
await ctx.send("Milestone does not exist.", hidden=True) await ctx.send("Milestone does not exist.", ephemeral=True)
return return
created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime(
@ -157,13 +157,13 @@ class GitlabCog(CacheCog):
scopes=guild_ids, scopes=guild_ids,
) )
@slash_option( @slash_option(
name="id", description="Merge Request ID", option_type=OptionTypes.INTEGER, required=True name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True
) )
async def _mergerequest(self, ctx: InteractionContext, id: int) -> None: async def _mergerequest(self, ctx: InteractionContext, id: int) -> None:
try: try:
mr = self.project.mergerequests.get(int(id)) mr = self.project.mergerequests.get(int(id))
except gitlab.exceptions.GitlabGetError: except gitlab.exceptions.GitlabGetError:
await ctx.send("Merge request does not exist.", hidden=True) await ctx.send("Merge request does not exist.", ephemeral=True)
return return
assignee = mr.assignee assignee = mr.assignee
if assignee: if assignee:
@ -266,7 +266,7 @@ class GitlabCog(CacheCog):
@slash_option( @slash_option(
name="state", name="state",
description="State of issues to get", description="State of issues to get",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Open", value="opened"), SlashCommandChoice(name="Open", value="opened"),
@ -277,10 +277,10 @@ class GitlabCog(CacheCog):
async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None: async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None:
exists = self.check_cache(ctx, state=state) exists = self.check_cache(ctx, state=state)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
await ctx.defer() await ctx.defer()
@ -339,7 +339,7 @@ class GitlabCog(CacheCog):
@slash_option( @slash_option(
name="state", name="state",
description="State of merge requests to get", description="State of merge requests to get",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Open", value="opened"), SlashCommandChoice(name="Open", value="opened"),
@ -350,10 +350,10 @@ class GitlabCog(CacheCog):
async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None: async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None:
exists = self.check_cache(ctx, state=state) exists = self.check_cache(ctx, state=state)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
"Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
await ctx.defer() await ctx.defer()
@ -414,10 +414,10 @@ class GitlabCog(CacheCog):
async def _milestones(self, ctx: InteractionContext) -> None: async def _milestones(self, ctx: InteractionContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
await ctx.defer() await ctx.defer()
@ -464,4 +464,4 @@ class GitlabCog(CacheCog):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists."""
if get_config().gitlab_token: if get_config().gitlab_token:
bot.add_cog(GitlabCog(bot)) GitlabCog(bot)

View file

@ -105,4 +105,4 @@ class ImageCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add ImageCog to J.A.R.V.I.S.""" """Add ImageCog to J.A.R.V.I.S."""
bot.add_cog(ImageCog(bot)) ImageCog(bot)

View file

@ -34,7 +34,7 @@ class JokeCog(Scale):
name="joke", name="joke",
description="Hear a joke", description="Hear a joke",
) )
@slash_option(name="id", description="Joke ID", required=False, option_type=OptionTypes.INTEGER) @slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER)
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10) @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10)
async def _joke(self, ctx: InteractionContext, id: str = None) -> None: async def _joke(self, ctx: InteractionContext, id: str = None) -> None:
"""Get a joke from the database.""" """Get a joke from the database."""
@ -57,7 +57,7 @@ class JokeCog(Scale):
result = Joke.objects().aggregate(pipeline).next() result = Joke.objects().aggregate(pipeline).next()
if result is None: if result is None:
await ctx.send("Humor module failed. Please try again later.", hidden=True) await ctx.send("Humor module failed. Please try again later.", ephemeral=True)
return return
emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"]) emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"])
for match in emotes: for match in emotes:
@ -118,4 +118,4 @@ class JokeCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add JokeCog to J.A.R.V.I.S.""" """Add JokeCog to J.A.R.V.I.S."""
bot.add_cog(JokeCog(bot)) JokeCog(bot)

View file

@ -6,6 +6,6 @@ from jarvis.cogs.modlog import command, member, message
def setup(bot: Bot) -> None: def setup(bot: Bot) -> None:
"""Add modlog cogs to J.A.R.V.I.S.""" """Add modlog cogs to J.A.R.V.I.S."""
bot.add_cog(command.ModlogCommandCog(bot)) command.ModlogCommandCog(bot)
bot.add_cog(member.ModlogMemberCog(bot)) member.ModlogMemberCog(bot)
bot.add_cog(message.ModlogMessageCog(bot)) message.ModlogMessageCog(bot)

View file

@ -2,6 +2,7 @@
from dis_snek import MessageContext, Scale, Snake, message_command from dis_snek import MessageContext, Scale, Snake, message_command
from dis_snek.models.discord.user import User from dis_snek.models.discord.user import User
from dis_snek.models.snek.checks import is_owner from dis_snek.models.snek.checks import is_owner
from dis_snek.models.snek.command import check
from jarvis.config import reload_config from jarvis.config import reload_config
from jarvis.db.models import Config from jarvis.db.models import Config
@ -19,7 +20,7 @@ class OwnerCog(Scale):
self.admins = Config.objects(key="admins").first() self.admins = Config.objects(key="admins").first()
@message_command(name="addadmin") @message_command(name="addadmin")
@is_owner() @check(is_owner())
async def _add(self, ctx: MessageContext, user: User) -> None: async def _add(self, ctx: MessageContext, user: User) -> None:
if user.id in self.admins.value: if user.id in self.admins.value:
await ctx.send(f"{user.mention} is already an admin.") await ctx.send(f"{user.mention} is already an admin.")
@ -43,4 +44,4 @@ class OwnerCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add OwnerCog to J.A.R.V.I.S.""" """Add OwnerCog to J.A.R.V.I.S."""
bot.add_cog(OwnerCog(bot)) OwnerCog(bot)

View file

@ -6,8 +6,6 @@ from typing import List, Optional
from bson import ObjectId from bson import ObjectId
from dis_snek import InteractionContext, Snake from dis_snek import InteractionContext, Snake
from dis_snek.ext.tasks.task import Task
from dis_snek.ext.tasks.triggers import IntervalTrigger
from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.components import ActionRow, Select, SelectOption
from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.embed import Embed, EmbedField
from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.application_commands import (
@ -32,34 +30,33 @@ class RemindmeCog(CacheCog):
def __init__(self, bot: Snake): def __init__(self, bot: Snake):
super().__init__(bot) super().__init__(bot)
self._remind.start()
@slash_command(name="remindme", description="Set a reminder") @slash_command(name="remindme", description="Set a reminder")
@slash_option( @slash_option(
name="message", name="message",
description="What to remind you of?", description="What to remind you of?",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
@slash_option( @slash_option(
name="weeks", name="weeks",
description="Number of weeks?", description="Number of weeks?",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@slash_option( @slash_option(
name="days", description="Number of days?", option_type=OptionTypes.INTEGER, required=False name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False
) )
@slash_option( @slash_option(
name="hours", name="hours",
description="Number of hours?", description="Number of hours?",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@slash_option( @slash_option(
name="minutes", name="minutes",
description="Number of minutes?", description="Number of minutes?",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
async def _remindme( async def _remindme(
@ -72,20 +69,20 @@ class RemindmeCog(CacheCog):
minutes: Optional[int] = 0, minutes: Optional[int] = 0,
) -> None: ) -> None:
if len(message) > 100: if len(message) > 100:
await ctx.send("Reminder cannot be > 100 characters.", hidden=True) await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True)
return return
elif invites.search(message): elif invites.search(message):
await ctx.send( await ctx.send(
"Listen, don't use this to try and bypass the rules", "Listen, don't use this to try and bypass the rules",
hidden=True, ephemeral=True,
) )
return return
elif not valid.fullmatch(message): elif not valid.fullmatch(message):
await ctx.send("Hey, you should probably make this readable", hidden=True) await ctx.send("Hey, you should probably make this readable", ephemeral=True)
return return
if not any([weeks, days, hours, minutes]): if not any([weeks, days, hours, minutes]):
await ctx.send("At least one time period is required", hidden=True) await ctx.send("At least one time period is required", ephemeral=True)
return return
weeks = abs(weeks) weeks = abs(weeks)
@ -94,19 +91,19 @@ class RemindmeCog(CacheCog):
minutes = abs(minutes) minutes = abs(minutes)
if weeks and weeks > 4: if weeks and weeks > 4:
await ctx.send("Cannot be farther than 4 weeks out!", hidden=True) await ctx.send("Cannot be farther than 4 weeks out!", ephemeral=True)
return return
elif days and days > 6: elif days and days > 6:
await ctx.send("Use weeks instead of 7+ days, please.", hidden=True) await ctx.send("Use weeks instead of 7+ days, please.", ephemeral=True)
return return
elif hours and hours > 23: elif hours and hours > 23:
await ctx.send("Use days instead of 24+ hours, please.", hidden=True) await ctx.send("Use days instead of 24+ hours, please.", ephemeral=True)
return return
elif minutes and minutes > 59: elif minutes and minutes > 59:
await ctx.send("Use hours instead of 59+ minutes, please.", hidden=True) await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True)
return return
reminders = Reminder.objects(user=ctx.author.id, active=True).count() reminders = Reminder.objects(user=ctx.author.id, active=True).count()
@ -114,7 +111,7 @@ class RemindmeCog(CacheCog):
await ctx.send( await ctx.send(
"You already have 5 (or more) active reminders. " "You already have 5 (or more) active reminders. "
"Please either remove an old one, or wait for one to pass", "Please either remove an old one, or wait for one to pass",
hidden=True, ephemeral=True,
) )
return return
@ -187,15 +184,15 @@ class RemindmeCog(CacheCog):
async def _list(self, ctx: InteractionContext) -> None: async def _list(self, ctx: InteractionContext) -> None:
exists = self.check_cache(ctx) exists = self.check_cache(ctx)
if exists: if exists:
await ctx.defer(hidden=True) await ctx.defer(ephemeral=True)
await ctx.send( await ctx.send(
f"Please use existing interaction: {exists['paginator']._message.jump_url}", f"Please use existing interaction: {exists['paginator']._message.jump_url}",
hidden=True, ephemeral=True,
) )
return return
reminders = Reminder.objects(user=ctx.author.id, active=True) reminders = Reminder.objects(user=ctx.author.id, active=True)
if not reminders: if not reminders:
await ctx.send("You have no reminders set.", hidden=True) await ctx.send("You have no reminders set.", ephemeral=True)
return return
embed = await self.get_reminders_embed(ctx, reminders) embed = await self.get_reminders_embed(ctx, reminders)
@ -206,7 +203,7 @@ class RemindmeCog(CacheCog):
async def _delete(self, ctx: InteractionContext) -> None: async def _delete(self, ctx: InteractionContext) -> None:
reminders = Reminder.objects(user=ctx.author.id, active=True) reminders = Reminder.objects(user=ctx.author.id, active=True)
if not reminders: if not reminders:
await ctx.send("You have no reminders set", hidden=True) await ctx.send("You have no reminders set", ephemeral=True)
return return
options = [] options = []
@ -279,36 +276,7 @@ class RemindmeCog(CacheCog):
component["disabled"] = True component["disabled"] = True
await message.edit(components=components) await message.edit(components=components)
@Task.create(trigger=IntervalTrigger(seconds=15))
async def _remind(self) -> None:
reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))
for reminder in reminders:
if reminder.remind_at <= datetime.utcnow():
user = await self.bot.fetch_user(reminder.user)
if not user:
reminder.delete()
continue
embed = build_embed(
title="You have a reminder",
description=reminder.message,
fields=[],
)
embed.set_author(
name=user.name + "#" + user.discriminator,
icon_url=user.display_avatar,
)
embed.set_thumbnail(url=user.display_avatar)
try:
await user.send(embed=embed)
except Exception:
guild = self.bot.fetch_guild(reminder.guild)
channel = guild.get_channel(reminder.channel) if guild else None
if channel:
await channel.send(f"{user.mention}", embed=embed)
finally:
reminder.delete()
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add RemindmeCog to J.A.R.V.I.S.""" """Add RemindmeCog to J.A.R.V.I.S."""
bot.add_cog(RemindmeCog(bot)) RemindmeCog(bot)

View file

@ -2,7 +2,6 @@
import asyncio import asyncio
from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek import InteractionContext, Permissions, Scale, Snake
from dis_snek.client.utils import get
from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.components import ActionRow, Select, SelectOption
from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.embed import EmbedField
from dis_snek.models.discord.role import Role from dis_snek.models.discord.role import Role
@ -11,11 +10,11 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.command import check, cooldown
from dis_snek.models.snek.cooldowns import Buckets from dis_snek.models.snek.cooldowns import Buckets
from jarvis.db.models import Rolegiver from jarvis.db.models import Rolegiver
from jarvis.utils import build_embed from jarvis.utils import build_embed, get
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
@ -28,21 +27,19 @@ class RolegiverCog(Scale):
@slash_command( @slash_command(
name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver" name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver"
) )
@slash_option( @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True)
name="role", description="Role to add", optin_type=OptionTypes.ROLE, required=True @check(admin_or_permissions(Permissions.MANAGE_GUILD))
)
@admin_or_permissions(Permissions.MANAGE_GUILD)
async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None: async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None:
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if setting and role.id in setting.roles: if setting and role.id in setting.roles:
await ctx.send("Role already in rolegiver", hidden=True) await ctx.send("Role already in rolegiver", ephemeral=True)
return return
if not setting: if not setting:
setting = Rolegiver(guild=ctx.guild.id, roles=[]) setting = Rolegiver(guild=ctx.guild.id, roles=[])
if len(setting.roles) >= 20: if len(setting.roles) >= 20:
await ctx.send("You can only have 20 roles in the rolegiver", hidden=True) await ctx.send("You can only have 20 roles in the rolegiver", ephemeral=True)
return return
setting.roles.append(role.id) setting.roles.append(role.id)
@ -84,11 +81,11 @@ class RolegiverCog(Scale):
@slash_command( @slash_command(
name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver" name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver"
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _rolegiver_remove(self, ctx: InteractionContext) -> None: async def _rolegiver_remove(self, ctx: InteractionContext) -> None:
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if not setting or (setting and not setting.roles): if not setting or (setting and not setting.roles):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", ephemeral=True)
return return
options = [] options = []
@ -168,7 +165,7 @@ class RolegiverCog(Scale):
async def _rolegiver_list(self, ctx: InteractionContext) -> None: async def _rolegiver_list(self, ctx: InteractionContext) -> None:
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if not setting or (setting and not setting.roles): if not setting or (setting and not setting.roles):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", ephemeral=True)
return return
roles = [] roles = []
@ -204,7 +201,7 @@ class RolegiverCog(Scale):
async def _role_get(self, ctx: InteractionContext) -> None: async def _role_get(self, ctx: InteractionContext) -> None:
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if not setting or (setting and not setting.roles): if not setting or (setting and not setting.roles):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", ephemeral=True)
return return
options = [] options = []
@ -283,10 +280,10 @@ class RolegiverCog(Scale):
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if not setting or (setting and not setting.roles): if not setting or (setting and not setting.roles):
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", ephemeral=True)
return return
elif not any(x.id in setting.roles for x in user_roles): elif not any(x.id in setting.roles for x in user_roles):
await ctx.send("You have no rolegiver roles", hidden=True) await ctx.send("You have no rolegiver roles", ephemeral=True)
return return
valid = list(filter(lambda x: x.id in setting.roles, user_roles)) valid = list(filter(lambda x: x.id in setting.roles, user_roles))
@ -361,11 +358,11 @@ class RolegiverCog(Scale):
@slash_command( @slash_command(
name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver"
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None:
setting = Rolegiver.objects(guild=ctx.guild.id).first() setting = Rolegiver.objects(guild=ctx.guild.id).first()
if not setting or not setting.roles: if not setting or not setting.roles:
await ctx.send("Rolegiver has no roles", hidden=True) await ctx.send("Rolegiver has no roles", ephemeral=True)
guild_role_ids = [r.id for r in ctx.guild.roles] guild_role_ids = [r.id for r in ctx.guild.roles]
for role_id in setting.roles: for role_id in setting.roles:
if role_id not in guild_role_ids: if role_id not in guild_role_ids:
@ -377,5 +374,4 @@ class RolegiverCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add RolegiverCog to J.A.R.V.I.S.""" """Add RolegiverCog to J.A.R.V.I.S."""
bot.add_cog(RolegiverCog(bot)) RolegiverCog(bot)
bot.add_cog(RolegiverCog(bot))

View file

@ -1,6 +1,5 @@
"""J.A.R.V.I.S. Starboard Cog.""" """J.A.R.V.I.S. Starboard Cog."""
from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek import InteractionContext, Permissions, Scale, Snake
from dis_snek.client.utils import find
from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.channel import GuildText
from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.components import ActionRow, Select, SelectOption
from dis_snek.models.discord.message import Message from dis_snek.models.discord.message import Message
@ -11,9 +10,10 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.db.models import Star, Starboard from jarvis.db.models import Star, Starboard
from jarvis.utils import build_embed from jarvis.utils import build_embed, find
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
supported_images = [ supported_images = [
@ -32,7 +32,7 @@ class StarboardCog(Scale):
self.bot = bot self.bot = bot
@slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards") @slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards")
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _list(self, ctx: InteractionContext) -> None: async def _list(self, ctx: InteractionContext) -> None:
starboards = Starboard.objects(guild=ctx.guild.id) starboards = Starboard.objects(guild=ctx.guild.id)
if starboards != []: if starboards != []:
@ -49,29 +49,29 @@ class StarboardCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Starboard channel", description="Starboard channel",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _create(self, ctx: InteractionContext, channel: GuildText) -> None: async def _create(self, ctx: InteractionContext, channel: GuildText) -> None:
if channel not in ctx.guild.channels: if channel not in ctx.guild.channels:
await ctx.send( await ctx.send(
"Channel not in guild. Choose an existing channel.", "Channel not in guild. Choose an existing channel.",
hidden=True, ephemeral=True,
) )
return return
if not isinstance(channel, GuildText): if not isinstance(channel, GuildText):
await ctx.send("Channel must be a GuildText", hidden=True) await ctx.send("Channel must be a GuildText", ephemeral=True)
return return
exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first() exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first()
if exists: if exists:
await ctx.send(f"Starboard already exists at {channel.mention}.", hidden=True) await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True)
return return
count = Starboard.objects(guild=ctx.guild.id).count() count = Starboard.objects(guild=ctx.guild.id).count()
if count >= 25: if count >= 25:
await ctx.send("25 starboard limit reached", hidden=True) await ctx.send("25 starboard limit reached", ephemeral=True)
return return
_ = Starboard( _ = Starboard(
@ -87,33 +87,33 @@ class StarboardCog(Scale):
@slash_option( @slash_option(
name="channel", name="channel",
description="Starboard channel", description="Starboard channel",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None: async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None:
deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete() deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete()
if deleted: if deleted:
_ = Star.objects(starboard=channel.id).delete() _ = Star.objects(starboard=channel.id).delete()
await ctx.send(f"Starboard deleted from {channel.mention}.", hidden=True) await ctx.send(f"Starboard deleted from {channel.mention}.", ephemeral=True)
else: else:
await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True) await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True)
@context_menu(name="Star Message", target=CommandTypes.MESSAGE) @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE)
async def _star_message(self, ctx: InteractionContext) -> None: async def _star_message(self, ctx: InteractionContext) -> None:
await self._star_add.invoke(ctx, ctx.target_message) await self._star_add.invoke(ctx, ctx.target_message)
@slash_command(name="star", sub_cmd_name="add", description="Star a message") @slash_command(name="star", sub_cmd_name="add", description="Star a message")
@slash_option( @slash_option(
name="message", description="Message to star", option_type=OptionTypes.STRING, required=True name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True
) )
@slash_option( @slash_option(
name="channel", name="channel",
description="Channel that has the message, not required if used in same channel", description="Channel that has the message, not required if used in same channel",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=False, required=False,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _star_add( async def _star_add(
self, self,
ctx: InteractionContext, ctx: InteractionContext,
@ -124,7 +124,7 @@ class StarboardCog(Scale):
channel = ctx.channel channel = ctx.channel
starboards = Starboard.objects(guild=ctx.guild.id) starboards = Starboard.objects(guild=ctx.guild.id)
if not starboards: if not starboards:
await ctx.send("No starboards exist.", hidden=True) await ctx.send("No starboards exist.", ephemeral=True)
return return
await ctx.defer() await ctx.defer()
@ -135,7 +135,7 @@ class StarboardCog(Scale):
message = await channel.get_message(int(message)) message = await channel.get_message(int(message))
if not message: if not message:
await ctx.send("Message not found", hidden=True) await ctx.send("Message not found", ephemeral=True)
return return
channel_list = [] channel_list = []
@ -174,7 +174,7 @@ class StarboardCog(Scale):
if exists: if exists:
await ctx.send( await ctx.send(
f"Message already sent to Starboard {starboard.mention}", f"Message already sent to Starboard {starboard.mention}",
hidden=True, ephemeral=True,
) )
return return
@ -229,15 +229,15 @@ class StarboardCog(Scale):
@slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message")
@slash_option( @slash_option(
name="message", description="Star to delete", option_type=OptionTypes.INTEGER, required=True name="message", description="Star to delete", opt_type=OptionTypes.INTEGER, required=True
) )
@slash_option( @slash_option(
name="starboard", name="starboard",
description="Starboard to delete star from", description="Starboard to delete star from",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _star_delete( async def _star_delete(
self, self,
ctx: InteractionContext, ctx: InteractionContext,
@ -245,13 +245,13 @@ class StarboardCog(Scale):
starboard: GuildText, starboard: GuildText,
) -> None: ) -> None:
if not isinstance(starboard, GuildText): if not isinstance(starboard, GuildText):
await ctx.send("Channel must be a GuildText channel", hidden=True) await ctx.send("Channel must be a GuildText channel", ephemeral=True)
return return
exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first() exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first()
if not exists: if not exists:
await ctx.send( await ctx.send(
f"Starboard does not exist in {starboard.mention}. Please create it first", f"Starboard does not exist in {starboard.mention}. Please create it first",
hidden=True, ephemeral=True,
) )
return return
@ -262,7 +262,7 @@ class StarboardCog(Scale):
active=True, active=True,
).first() ).first()
if not star: if not star:
await ctx.send(f"No star exists with id {id}", hidden=True) await ctx.send(f"No star exists with id {id}", ephemeral=True)
return return
message = await starboard.fetch_message(star.star) message = await starboard.fetch_message(star.star)
@ -277,5 +277,4 @@ class StarboardCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add StarboardCog to J.A.R.V.I.S.""" """Add StarboardCog to J.A.R.V.I.S."""
bot.add_cog(StarboardCog(bot)) StarboardCog(bot)
bot.add_cog(StarboardCog(bot))

View file

@ -1,12 +1,9 @@
"""J.A.R.V.I.S. Twitter Cog.""" """J.A.R.V.I.S. Twitter Cog."""
import asyncio import asyncio
import logging
import tweepy import tweepy
from bson import ObjectId from bson import ObjectId
from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek import InteractionContext, Permissions, Scale, Snake
from dis_snek.ext.tasks.task import Task
from dis_snek.ext.tasks.triggers import IntervalTrigger
from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.channel import GuildText
from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.components import ActionRow, Select, SelectOption
from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.application_commands import (
@ -15,13 +12,12 @@ from dis_snek.models.snek.application_commands import (
slash_command, slash_command,
slash_option, slash_option,
) )
from dis_snek.models.snek.command import check
from jarvis.config import get_config from jarvis.config import get_config
from jarvis.db.models import Twitter from jarvis.db.models import Twitter
from jarvis.utils.permissions import admin_or_permissions from jarvis.utils.permissions import admin_or_permissions
logger = logging.getLogger("discord")
class TwitterCog(Scale): class TwitterCog(Scale):
"""J.A.R.V.I.S. Twitter Cog.""" """J.A.R.V.I.S. Twitter Cog."""
@ -33,102 +29,59 @@ class TwitterCog(Scale):
config.twitter["consumer_key"], config.twitter["consumer_secret"] config.twitter["consumer_key"], config.twitter["consumer_secret"]
) )
self.api = tweepy.API(auth) self.api = tweepy.API(auth)
self._tweets.start()
self._guild_cache = {} self._guild_cache = {}
self._channel_cache = {} self._channel_cache = {}
@Task.create(trigger=IntervalTrigger(minutes=1))
async def _tweets(self) -> None:
twitters = Twitter.objects(active=True)
handles = Twitter.objects.distinct("handle")
twitter_data = {}
for handle in handles:
try:
data = await asyncio.to_thread(self.api.user_timeline, screen_name=handle)
twitter_data[handle] = data
except Exception as e:
logger.error(f"Error with fetching: {e}")
for twitter in twitters:
try:
tweets = list(
filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle])
)
if tweets:
guild_id = twitter.guild
channel_id = twitter.channel
tweets = sorted(tweets, key=lambda x: x.id)
if guild_id not in self._guild_cache:
self._guild_cache[guild_id] = await self.bot.get_guild(guild_id)
guild = self._guild_cache[twitter.guild]
if channel_id not in self._channel_cache:
self._channel_cache[channel_id] = await guild.fetch_channel(channel_id)
channel = self._channel_cache[channel_id]
for tweet in tweets:
retweet = "retweeted_status" in tweet.__dict__
if retweet and not twitter.retweets:
continue
timestamp = int(tweet.created_at.timestamp())
url = f"https://twitter.com/{twitter.handle}/status/{tweet.id}"
verb = "re" if retweet else ""
await channel.send(
f"`@{twitter.handle}` {verb}tweeted this at <t:{timestamp}:f>: {url}"
)
newest = max(tweets, key=lambda x: x.id)
twitter.last_tweet = newest.id
twitter.save()
except Exception as e:
logger.error(f"Error with tweets: {e}")
@slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount")
@slash_option( @slash_option(
name="handle", description="Twitter account", option_type=OptionTypes.STRING, required=True name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True
) )
@slash_option( @slash_option(
name="channel", name="channel",
description="Channel to post tweets to", description="Channel to post tweets to",
option_type=OptionTypes.CHANNEL, opt_type=OptionTypes.CHANNEL,
required=True, required=True,
) )
@slash_option( @slash_option(
name="retweets", name="retweets",
description="Mirror re-tweets?", description="Mirror re-tweets?",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="Yes", value="Yes"),
SlashCommandChoice(name="No", value="No"), SlashCommandChoice(name="No", value="No"),
], ],
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _twitter_follow( async def _twitter_follow(
self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes" self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes"
) -> None: ) -> None:
handle = handle.lower() handle = handle.lower()
retweets = retweets == "Yes" retweets = retweets == "Yes"
if len(handle) > 15: if len(handle) > 15:
await ctx.send("Invalid Twitter handle", hidden=True) await ctx.send("Invalid Twitter handle", ephemeral=True)
return return
if not isinstance(channel, GuildText): if not isinstance(channel, GuildText):
await ctx.send("Channel must be a text channel", hidden=True) await ctx.send("Channel must be a text channel", ephemeral=True)
return return
try: try:
latest_tweet = await asyncio.to_thread(self.api.user_timeline, screen_name=handle)[0] latest_tweet = await asyncio.to_thread(self.api.user_timeline, screen_name=handle)[0]
except Exception: except Exception:
await ctx.send( await ctx.send(
"Unable to get user timeline. Are you sure the handle is correct?", hidden=True "Unable to get user timeline. Are you sure the handle is correct?", ephemeral=True
) )
return return
count = Twitter.objects(guild=ctx.guild.id).count() count = Twitter.objects(guild=ctx.guild.id).count()
if count >= 12: if count >= 12:
await ctx.send("Cannot follow more than 12 Twitter accounts", hidden=True) await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True)
return return
exists = Twitter.objects(handle=handle, guild=ctx.guild.id) exists = Twitter.objects(handle=handle, guild=ctx.guild.id)
if exists: if exists:
await ctx.send("Twitter handle already being followed in this guild", hidden=True) await ctx.send("Twitter handle already being followed in this guild", ephemeral=True)
return return
t = Twitter( t = Twitter(
@ -145,11 +98,11 @@ class TwitterCog(Scale):
await ctx.send(f"Now following `@{handle}` in {channel.mention}") await ctx.send(f"Now following `@{handle}` in {channel.mention}")
@slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts") @slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts")
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _twitter_unfollow(self, ctx: InteractionContext) -> None: async def _twitter_unfollow(self, ctx: InteractionContext) -> None:
twitters = Twitter.objects(guild=ctx.guild.id) twitters = Twitter.objects(guild=ctx.guild.id)
if not twitters: if not twitters:
await ctx.send("You need to follow a Twitter account first", hidden=True) await ctx.send("You need to follow a Twitter account first", ephemeral=True)
return return
options = [] options = []
@ -198,19 +151,19 @@ class TwitterCog(Scale):
@slash_option( @slash_option(
name="retweets", name="retweets",
description="Mirror re-tweets?", description="Mirror re-tweets?",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="Yes", value="Yes"),
SlashCommandChoice(name="No", value="No"), SlashCommandChoice(name="No", value="No"),
], ],
) )
@admin_or_permissions(Permissions.MANAGE_GUILD) @check(admin_or_permissions(Permissions.MANAGE_GUILD))
async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None:
retweets = retweets == "Yes" retweets = retweets == "Yes"
twitters = Twitter.objects(guild=ctx.guild.id) twitters = Twitter.objects(guild=ctx.guild.id)
if not twitters: if not twitters:
await ctx.send("You need to follow a Twitter account first", hidden=True) await ctx.send("You need to follow a Twitter account first", ephemeral=True)
return return
options = [] options = []
@ -265,4 +218,4 @@ class TwitterCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add TwitterCog to J.A.R.V.I.S.""" """Add TwitterCog to J.A.R.V.I.S."""
bot.add_cog(TwitterCog(bot)) TwitterCog(bot)

View file

@ -6,7 +6,8 @@ from io import BytesIO
import numpy as np import numpy as np
from dis_snek import InteractionContext, Scale, Snake, const from dis_snek import InteractionContext, Scale, Snake, const
from dis_snek.models.discord.embed import Color, EmbedField from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice
from dis_snek.models.discord.embed import EmbedField
from dis_snek.models.discord.file import File from dis_snek.models.discord.file import File
from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.guild import Guild
from dis_snek.models.discord.role import Role from dis_snek.models.discord.role import Role
@ -41,7 +42,7 @@ class UtilCog(Scale):
self.bot = bot self.bot = bot
self.config = get_config() self.config = get_config()
@slash_command(name="Status", description="Retrieve J.A.R.V.I.S. status") @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status")
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
async def _status(self, ctx: InteractionContext) -> None: async def _status(self, ctx: InteractionContext) -> None:
title = "J.A.R.V.I.S. Status" title = "J.A.R.V.I.S. Status"
@ -49,7 +50,7 @@ class UtilCog(Scale):
color = "#3498db" color = "#3498db"
fields = [] fields = []
fields.append(EmbedField(name="discord.py", value=const.__version__)) fields.append(EmbedField(name="dis-snek", value=const.__version__))
fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False))
fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False))
embed = build_embed(title=title, description=desc, fields=fields, color=color) embed = build_embed(title=title, description=desc, fields=fields, color=color)
@ -64,7 +65,7 @@ class UtilCog(Scale):
with BytesIO() as image_bytes: with BytesIO() as image_bytes:
JARVIS_LOGO.save(image_bytes, "PNG") JARVIS_LOGO.save(image_bytes, "PNG")
image_bytes.seek(0) image_bytes.seek(0)
logo = File(image_bytes, filename="logo.png") logo = File(image_bytes, file_name="logo.png")
await ctx.send(file=logo) await ctx.send(file=logo)
@slash_command(name="rchk", description="Robot Camo HK416") @slash_command(name="rchk", description="Robot Camo HK416")
@ -78,13 +79,13 @@ class UtilCog(Scale):
@slash_option( @slash_option(
name="text", name="text",
description="Text to camo-ify", description="Text to camo-ify",
option_type=OptionTypes.STRING, opt_type=OptionTypes.STRING,
required=True, required=True,
) )
async def _rcauto(self, ctx: InteractionContext, text: str) -> None: async def _rcauto(self, ctx: InteractionContext, text: str) -> None:
to_send = "" to_send = ""
if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()): if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()):
await ctx.send("Please use ASCII characters.", hidden=True) await ctx.send("Please use ASCII characters.", ephemeral=True)
return return
for letter in text.upper(): for letter in text.upper():
if letter == " ": if letter == " ":
@ -96,7 +97,7 @@ class UtilCog(Scale):
else: else:
to_send += f"<:{names[id]}:{id}>" to_send += f"<:{names[id]}:{id}>"
if len(to_send) > 2000: if len(to_send) > 2000:
await ctx.send("Too long.", hidden=True) await ctx.send("Too long.", ephemeral=True)
else: else:
await ctx.send(to_send) await ctx.send(to_send)
@ -104,7 +105,7 @@ class UtilCog(Scale):
@slash_option( @slash_option(
name="user", name="user",
description="User to view avatar of", description="User to view avatar of",
option_type=OptionTypes.USER, opt_type=OptionTypes.USER,
required=False, required=False,
) )
@cooldown(bucket=Buckets.USER, rate=1, interval=5) @cooldown(bucket=Buckets.USER, rate=1, interval=5)
@ -112,7 +113,7 @@ class UtilCog(Scale):
if not user: if not user:
user = ctx.author user = ctx.author
avatar = user.display_avatar avatar = user.display_avatar.url
embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE") embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE")
embed.set_image(url=avatar) embed.set_image(url=avatar)
embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar) embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar)
@ -125,25 +126,25 @@ class UtilCog(Scale):
@slash_option( @slash_option(
name="role", name="role",
description="Role to get info of", description="Role to get info of",
option_type=OptionTypes.ROLE, opt_type=OptionTypes.ROLE,
required=True, required=True,
) )
async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None: async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None:
fields = [ fields = [
EmbedField(name="ID", value=role.id), EmbedField(name="ID", value=str(role.id), inline=True),
EmbedField(name="Name", value=role.name), EmbedField(name="Name", value=role.name, inline=True),
EmbedField(name="Color", value=str(role.color)), EmbedField(name="Color", value=str(role.color.hex), inline=True),
EmbedField(name="Mention", value=f"`{role.mention}`"), EmbedField(name="Mention", value=f"`{role.mention}`", inline=True),
EmbedField(name="Hoisted", value="Yes" if role.hoist else "No"), EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True),
EmbedField(name="Position", value=str(role.position)), EmbedField(name="Position", value=str(role.position), inline=True),
EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No"), EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True),
EmbedField(name="Member Count", value=role.members), EmbedField(name="Member Count", value=str(len(role.members)), inline=True),
] ]
embed = build_embed( embed = build_embed(
title="", title="",
description="", description="",
fields=fields, fields=fields,
color=Color.from_hex(role.color), color=role.color,
timestamp=role.created_at, timestamp=role.created_at,
) )
embed.set_footer(text="Role Created") embed.set_footer(text="Role Created")
@ -154,14 +155,14 @@ class UtilCog(Scale):
fill = a > 0 fill = a > 0
data[..., :-1][fill.T] = list(role.color.to_rgb()) data[..., :-1][fill.T] = list(role.color.rgb)
im = Image.fromarray(data) im = Image.fromarray(data)
with BytesIO() as image_bytes: with BytesIO() as image_bytes:
im.save(image_bytes, "PNG") im.save(image_bytes, "PNG")
image_bytes.seek(0) image_bytes.seek(0)
color_show = File(image_bytes, filename="color_show.png") color_show = File(image_bytes, file_name="color_show.png")
await ctx.send(embed=embed, file=color_show) await ctx.send(embed=embed, file=color_show)
@ -172,7 +173,7 @@ class UtilCog(Scale):
@slash_option( @slash_option(
name="user", name="user",
description="User to get info of", description="User to get info of",
option_type=OptionTypes.USER, opt_type=OptionTypes.USER,
required=False, required=False,
) )
async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None:
@ -181,15 +182,15 @@ class UtilCog(Scale):
user_roles = user.roles user_roles = user.roles
if user_roles: if user_roles:
user_roles = sorted(user.roles, key=lambda x: -x.position) user_roles = sorted(user.roles, key=lambda x: -x.position)
_ = user_roles.pop(-1)
fields = [ fields = [
EmbedField( EmbedField(
name="Joined", name="Joined",
value=user.joined_at.strftime("%a, %b %-d, %Y %-I:%M %p"), value=user.joined_at.strftime("%a, %b %#d, %Y %#I:%M %p"),
), ),
EmbedField( EmbedField(
name="Registered", name="Registered",
value=user.created_at.strftime("%a, %b %-d, %Y %-I:%M %p"), value=user.created_at.strftime("%a, %b %#d, %Y %#I:%M %p"),
), ),
EmbedField( EmbedField(
name=f"Roles [{len(user_roles)}]", name=f"Roles [{len(user_roles)}]",
@ -202,13 +203,13 @@ class UtilCog(Scale):
title="", title="",
description=user.mention, description=user.mention,
fields=fields, fields=fields,
color=Color.from_hex(str(user_roles[0].color) if user_roles else "#3498db"), color=str(user_roles[0].color) if user_roles else "#3498db",
) )
embed.set_author( embed.set_author(
name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar.url
) )
embed.set_thumbnail(url=user.display_avatar) embed.set_thumbnail(url=user.display_avatar.url)
embed.set_footer(text=f"ID: {user.id}") embed.set_footer(text=f"ID: {user.id}")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -217,34 +218,35 @@ class UtilCog(Scale):
async def _server_info(self, ctx: InteractionContext) -> None: async def _server_info(self, ctx: InteractionContext) -> None:
guild: Guild = ctx.guild guild: Guild = ctx.guild
owner = ( owner = await guild.get_owner()
f"{guild.owner.display_name}#{guild.owner.discriminator}"
if guild.owner
else "||`[redacted]`||"
)
categories = len(guild.categories) owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||"
text_channels = len(guild.text_channels)
voice_channels = len(guild.voice_channels) categories = len([x for x in guild.channels if isinstance(x, GuildCategory)])
text_channels = len([x for x in guild.channels if isinstance(x, GuildText)])
voice_channels = len([x for x in guild.channels if isinstance(x, GuildVoice)])
threads = len(guild.threads)
members = guild.member_count members = guild.member_count
roles = len(guild.roles) roles = len(guild.roles)
role_list = ", ".join(role.name for role in guild.roles) role_list = sorted(guild.roles, key=lambda x: x.position, reverse=True)
role_list = ", ".join(role.mention for role in role_list)
fields = [ fields = [
EmbedField(name="Owner", value=owner), EmbedField(name="Owner", value=owner, inline=True),
EmbedField(name="Channel Categories", value=categories), EmbedField(name="Channel Categories", value=str(categories), inline=True),
EmbedField(name="Text Channels", value=text_channels), EmbedField(name="Text Channels", value=str(text_channels), inline=True),
EmbedField(name="Voice Channels", value=voice_channels), EmbedField(name="Voice Channels", value=str(voice_channels), inline=True),
EmbedField(name="Members", value=members), EmbedField(name="Threads", value=str(threads), inline=True),
EmbedField(name="Roles", value=roles), EmbedField(name="Members", value=str(members), inline=True),
EmbedField(name="Roles", value=str(roles), inline=True),
] ]
if len(role_list) < 1024: if len(role_list) < 1024:
fields.append(EmbedField(name="Role List", value=role_list, inline=False)) fields.append(EmbedField(name="Role List", value=role_list, inline=False))
embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at) embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at)
embed.set_author(name=guild.name, icon_url=guild.icon_url) embed.set_author(name=guild.name, icon_url=guild.icon.url)
embed.set_thumbnail(url=guild.icon_url) embed.set_thumbnail(url=guild.icon.url)
embed.set_footer(text=f"ID: {guild.id} | Server Created") embed.set_footer(text=f"ID: {guild.id} | Server Created")
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -258,13 +260,13 @@ class UtilCog(Scale):
@slash_option( @slash_option(
name="length", name="length",
description="Password length (default 32)", description="Password length (default 32)",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
) )
@slash_option( @slash_option(
name="chars", name="chars",
description="Characters to include (default last option)", description="Characters to include (default last option)",
option_type=OptionTypes.INTEGER, opt_type=OptionTypes.INTEGER,
required=False, required=False,
choices=[ choices=[
SlashCommandChoice(name="A-Za-z", value=0), SlashCommandChoice(name="A-Za-z", value=0),
@ -276,7 +278,7 @@ class UtilCog(Scale):
@cooldown(bucket=Buckets.USER, rate=1, interval=15) @cooldown(bucket=Buckets.USER, rate=1, interval=15)
async def _pw_gen(self, ctx: InteractionContext, length: int = 32, chars: int = 3) -> None: async def _pw_gen(self, ctx: InteractionContext, length: int = 32, chars: int = 3) -> None:
if length > 256: if length > 256:
await ctx.send("Please limit password to 256 characters", hidden=True) await ctx.send("Please limit password to 256 characters", ephemeral=True)
return return
choices = [ choices = [
string.ascii_letters, string.ascii_letters,
@ -290,12 +292,12 @@ class UtilCog(Scale):
f"Generated password:\n`{pw}`\n\n" f"Generated password:\n`{pw}`\n\n"
'**WARNING: Once you press "Dismiss Message", ' '**WARNING: Once you press "Dismiss Message", '
"*the password is lost forever***", "*the password is lost forever***",
hidden=True, ephemeral=True,
) )
@slash_command(name="pigpen", description="Encode a string into pigpen") @slash_command(name="pigpen", description="Encode a string into pigpen")
@slash_option( @slash_option(
name="text", description="Text to encode", option_type=OptionTypes.STRING, required=True name="text", description="Text to encode", opt_type=OptionTypes.STRING, required=True
) )
async def _pigpen(self, ctx: InteractionContext, text: str) -> None: async def _pigpen(self, ctx: InteractionContext, text: str) -> None:
outp = "`" outp = "`"
@ -314,4 +316,4 @@ class UtilCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add UtilCog to J.A.R.V.I.S.""" """Add UtilCog to J.A.R.V.I.S."""
bot.add_cog(UtilCog(bot)) UtilCog(bot)

View file

@ -89,4 +89,4 @@ class VerifyCog(Scale):
def setup(bot: Snake) -> None: def setup(bot: Snake) -> None:
"""Add VerifyCog to J.A.R.V.I.S.""" """Add VerifyCog to J.A.R.V.I.S."""
bot.add_cog(VerifyCog(bot)) VerifyCog(bot)