From c00829f9492f3689db41f6a82797a1523b4165f8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 21:45:42 -0600 Subject: [PATCH 01/95] Fix GitLab key error on author name --- jarvis/cogs/gitlab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index f23062b..074a33f 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -85,7 +85,7 @@ class GitlabCog(Scale): ) embed.set_author( name=issue.author["name"], - icon_url=issue.author["display_avatar"], + icon_url=issue.author["avatar_url"], url=issue.author["web_url"], ) embed.set_thumbnail( @@ -219,7 +219,7 @@ class GitlabCog(Scale): ) embed.set_author( name=mr.author["name"], - icon_url=mr.author["display_avatar"], + icon_url=mr.author["avatar_url"], url=mr.author["web_url"], ) embed.set_thumbnail( From 31df95809fa5fab6523ab21eadacc4cfc103188c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 22:58:48 -0600 Subject: [PATCH 02/95] Allow users to open issues from Discord, closes #140 --- jarvis/cogs/{gitlab.py => gl.py} | 78 +++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 12 deletions(-) rename jarvis/cogs/{gitlab.py => gl.py} (83%) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gl.py similarity index 83% rename from jarvis/cogs/gitlab.py rename to jarvis/cogs/gl.py index 074a33f..cdd08a4 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gl.py @@ -1,16 +1,20 @@ """J.A.R.V.I.S. GitLab Cog.""" +import asyncio from datetime import datetime import gitlab from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, slash_command, slash_option, ) +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.config import get_config from jarvis.utils import build_embed @@ -29,7 +33,11 @@ class GitlabCog(Scale): self.project = self._gitlab.projects.get(29) @slash_command( - name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids + name="gl", + description="Get GitLab info", + sub_cmd_name="issue", + sub_cmd_description="Get an issue from GitLab", + scopes=guild_ids, ) @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) async def _issue(self, ctx: InteractionContext, id: int) -> None: @@ -96,7 +104,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="milestone", - description="Get a milestone from GitLab", + sub_cmd_description="Get a milestone from GitLab", scopes=guild_ids, ) @slash_option( @@ -152,7 +160,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="mr", - description="Get a merge request from GitLab", + sub_cmd_description="Get a merge request from GitLab", scopes=guild_ids, ) @slash_option( @@ -181,25 +189,26 @@ class GitlabCog(Scale): labels = "None" fields = [ - EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:]), - EmbedField(name="Assignee", value=assignee), - EmbedField(name="Labels", value=labels), + EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:], inline=True), + EmbedField(name="Draft?", value=str(mr.draft), inline=True), + EmbedField(name="Assignee", value=assignee, inline=True), + EmbedField(name="Labels", value=labels, inline=True), ] if mr.labels: color = self.project.labels.get(mr.labels[0]).color else: color = "#00FFEE" - fields.append(EmbedField(name="Created At", value=created_at)) + fields.append(EmbedField(name="Created At", value=created_at, inline=True)) if mr.state == "merged": merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( "%Y-%m-%d %H:%M:%S UTC" ) - fields.append(EmbedField(name="Merged At", value=merged_at)) + fields.append(EmbedField(name="Merged At", value=merged_at, inline=True)) elif mr.state == "closed": closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( "%Y-%m-%d %H:%M:%S UTC" ) - fields.append(EmbedField(name="Closed At", value=closed_at)) + fields.append(EmbedField(name="Closed At", value=closed_at, inline=True)) if mr.milestone: fields.append( EmbedField( @@ -261,7 +270,10 @@ class GitlabCog(Scale): return embed @slash_command( - name="gl", sub_cmd_name="issues", description="Get issues from GitLab", scopes=guild_ids + name="gl", + sub_cmd_name="issues", + sub_cmd_description="Get issues from GitLab", + scopes=guild_ids, ) @slash_option( name="state", @@ -316,7 +328,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="mrs", - description="Get merge requests from GitLab", + sub_cmd_description="Get merge requests from GitLab", scopes=guild_ids, ) @slash_option( @@ -374,7 +386,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="milestones", - description="Get milestones from GitLab", + sub_cmd_description="Get milestones from GitLab", scopes=guild_ids, ) async def _milestones(self, ctx: InteractionContext) -> None: @@ -410,6 +422,48 @@ class GitlabCog(Scale): await paginator.send(ctx) + @slash_command( + name="gl", sub_cmd_name="report", sub_cmd_description="Report an issue", scopes=guild_ids + ) + @cooldown(bucket=Buckets.USER, rate=1, interval=600) + async def _open_issue(self, ctx: InteractionContext) -> None: + modal = Modal( + title="Open a new issue on GitLab", + components=[ + InputText( + label="Issue Title", + placeholder="Descriptive Title", + style=TextStyles.SHORT, + custom_id="title", + max_length=200, + ), + InputText( + label="Description (supports Markdown!)", + placeholder="Detailed Description", + style=TextStyles.PARAGRAPH, + custom_id="description", + ), + ], + ) + await ctx.send_modal(modal) + try: + resp = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + title = resp.responses.get("title") + desc = resp.responses.get("description") + except asyncio.TimeoutError: + return + if not title.startswith("[Discord]"): + title = "[Discord] " + title + desc = f"Opened by `@{ctx.author.username}` on Discord\n\n" + desc + issue = self.project.issues.create(data={"title": title, "description": desc}) + embed = build_embed( + title=f"Issue #{issue.id} Created", + description=("Thank you for opening an issue!\n\n[View it online]({issue['web_url']})"), + fields=[], + color="#00FFEE", + ) + await resp.send(embed=embed) + def setup(bot: Snake) -> None: """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" From 1fc0aec8f60baec38a47a839012f1f0d15f58d6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 20 Mar 2022 00:26:54 -0600 Subject: [PATCH 03/95] Add thread option to autoreact --- jarvis/client.py | 5 +++ jarvis/cogs/autoreact.py | 74 +++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 80c1c9d..c0ebc81 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -181,6 +181,11 @@ class Jarvis(Snake): if autoreact: for reaction in autoreact.reactions: await message.add_reaction(reaction) + if autoreact.thread: + name = message.content + if len(name) > 100: + name = name[:97] + "..." + await message.create_thread(name=message.content, reason="Autoreact") async def checks(self, message: Message) -> None: """Other message checks.""" diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index d5d127b..b5caa19 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -26,7 +26,7 @@ class AutoReactCog(Scale): self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") async def create_autoreact( - self, ctx: InteractionContext, channel: GuildText + self, ctx: InteractionContext, channel: GuildText, thread: bool ) -> Tuple[bool, Optional[str]]: """ Create an autoreact monitor on a channel. @@ -34,6 +34,7 @@ class AutoReactCog(Scale): Args: ctx: Interaction context of command channel: Channel to monitor + thread: Create a thread Returns: Tuple of success? and error message @@ -42,12 +43,13 @@ class AutoReactCog(Scale): if exists: return False, f"Autoreact already exists for {channel.mention}." - _ = Autoreact( + await Autoreact( guild=ctx.guild.id, channel=channel.id, reactions=[], + thread=thread, admin=ctx.author.id, - ).save() + ).commit() return True, None @@ -62,7 +64,11 @@ class AutoReactCog(Scale): Returns: Success? """ - return Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() is not None + ar = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) + if ar: + await ar.delete() + return True + return False @slash_command( name="autoreact", @@ -76,43 +82,55 @@ class AutoReactCog(Scale): required=True, ) @slash_option( - name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True + name="thread", description="Create a thread?", opt_type=OptionTypes.BOOLEAN, required=False + ) + @slash_option( + name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=False ) @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, thread: bool = True, emote: str = None + ) -> None: await ctx.defer() - custom_emoji = self.custom_emote.match(emote) - standard_emoji = emote in emoji_list - if not custom_emoji and not standard_emoji: - await ctx.send( - "Please use either an emote from this server or a unicode emoji.", - ephemeral=True, - ) - return - if custom_emoji: - emoji_id = int(custom_emoji.group(1)) - if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): - await ctx.send("Please use a custom emote from this server.", ephemeral=True) + if emote: + custom_emoji = self.custom_emote.match(emote) + standard_emoji = emote in emoji_list + if not custom_emoji and not standard_emoji: + await ctx.send( + "Please use either an emote from this server or a unicode emoji.", + ephemeral=True, + ) return + if custom_emoji: + emoji_id = int(custom_emoji.group(1)) + if not find(lambda x: x.id == emoji_id, await ctx.guild.fetch_all_custom_emojis()): + await ctx.send("Please use a custom emote from this server.", ephemeral=True) + return autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: - self.create_autoreact(ctx, channel) + await self.create_autoreact(ctx, channel, thread) autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) - if emote in autoreact.reactions: + if emote and emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", ephemeral=True, ) return - if len(autoreact.reactions) >= 5: + if emote and len(autoreact.reactions) >= 5: await ctx.send( "Max number of reactions hit. Remove a different one to add this one", ephemeral=True, ) return - autoreact.reactions.append(emote) - autoreact.save() - await ctx.send(f"Added {emote} to {channel.mention} autoreact.") + if emote: + autoreact.reactions.append(emote) + autoreact.thread = thread + await autoreact.commit() + message = "" + if emote: + message += f" Added {emote} to {channel.mention} autoreact." + message += f" Set autoreact thread creation to {thread} in {channel.mention}" + await ctx.send(message) @slash_command( name="autoreact", @@ -143,7 +161,7 @@ class AutoReactCog(Scale): ) return if emote.lower() == "all": - self.delete_autoreact(ctx, channel) + await self.delete_autoreact(ctx, channel) await ctx.send(f"Autoreact removed from {channel.mention}") elif emote not in autoreact.reactions: await ctx.send( @@ -153,9 +171,9 @@ class AutoReactCog(Scale): return else: autoreact.reactions.remove(emote) - autoreact.save() - if len(autoreact.reactions) == 0: - self.delete_autoreact(ctx, channel) + await autoreact.commit() + if len(autoreact.reactions) == 0 and not autoreact.thread: + await self.delete_autoreact(ctx, channel) await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") @slash_command( From 54dd6f40a88b24f21617c613bf2b5f6ab22b780b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 20 Mar 2022 02:00:29 -0600 Subject: [PATCH 04/95] Fix command descriptions --- jarvis/cogs/admin/roleping.py | 5 ++++- jarvis/cogs/rolegiver.py | 5 ++++- jarvis/cogs/starboard.py | 18 +++++++++++++++--- jarvis/cogs/twitter.py | 15 ++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index a9bf020..38a3c87 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -22,7 +22,10 @@ class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" @slash_command( - name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" + name="roleping", + description="Set up warnings for pinging specific roles", + sub_cmd_name="add", + sub_cmd_description="Add a role to roleping", ) @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 7a115b9..ea6b148 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -27,7 +27,10 @@ class RolegiverCog(Scale): self.bot = bot @slash_command( - name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver" + name="rolegiver", + description="Allow users to choose their own roles", + sub_cmd_name="add", + sub_cmd_description="Add a role to rolegiver", ) @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 8ae8da8..871e25c 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -33,7 +33,12 @@ class StarboardCog(Scale): def __init__(self, bot: Snake): self.bot = bot - @slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards") + @slash_command( + name="starboard", + description="Extra pins! Manage starboards", + sub_cmd_name="list", + sub_cmd_description="List all starboards", + ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _list(self, ctx: InteractionContext) -> None: starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) @@ -221,7 +226,12 @@ class StarboardCog(Scale): async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add(ctx, message=str(ctx.target_id)) - @slash_command(name="star", sub_cmd_name="add", description="Star a message") + @slash_command( + name="star", + description="Manage stars", + sub_cmd_name="add", + sub_cmd_description="Star a message", + ) @slash_option( name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True ) @@ -237,7 +247,9 @@ class StarboardCog(Scale): ) -> None: await self._star_add(ctx, message, channel) - @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") + @slash_command( + name="star", sub_cmd_name="delete", sub_cmd_description="Delete a starred message" + ) @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True ) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index dc7d875..836b57b 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -33,7 +33,12 @@ class TwitterCog(Scale): self._guild_cache = {} self._channel_cache = {} - @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") + @slash_command( + name="twitter", + description="Manage Twitter follows", + sub_cmd_name="follow", + sub_cmd_description="Follow a Twitter acount", + ) @slash_option( name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True ) @@ -107,7 +112,9 @@ class TwitterCog(Scale): 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", sub_cmd_description="Unfollow Twitter accounts" + ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) @@ -162,7 +169,9 @@ class TwitterCog(Scale): await message.edit(components=components) @slash_command( - name="twitter", sub_cmd_name="retweets", description="Modify followed Twitter accounts" + name="twitter", + sub_cmd_name="retweets", + sub_cmd_description="Modify followed Twitter accounts", ) @slash_option( name="retweets", From e80afc97ad0d2001a5ab8cf0463cdaf8408fd789 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:23:47 -0600 Subject: [PATCH 05/95] fix lock and lockdown cogs --- jarvis/cogs/admin/lock.py | 228 +++++++++++++++++----------------- jarvis/cogs/admin/lockdown.py | 214 +++++++++++++++++++------------ 2 files changed, 252 insertions(+), 190 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index e17cf58..eefdada 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,113 +1,117 @@ """J.A.R.V.I.S. LockCog.""" -# from dis_snek import Scale -# -# # TODO: Uncomment 99% of code once implementation is figured out -# from contextlib import suppress -# from typing import Union -# -# from dis_snek import InteractionContext, Scale, Snake -# from dis_snek.models.discord.enums import Permissions -# from dis_snek.models.discord.role import Role -# from dis_snek.models.discord.user import User -# from dis_snek.models.discord.channel import GuildText, GuildVoice, PermissionOverwrite -# from dis_snek.models.snek.application_commands import ( -# OptionTypes, -# PermissionTypes, -# slash_command, -# slash_option, -# ) -# from dis_snek.models.snek.command import check -# -# from jarvis.db.models import Lock -# from jarvis.utils.permissions import admin_or_permissions -# -# -# class LockCog(Scale): -# """J.A.R.V.I.S. LockCog.""" -# -# @slash_command(name="lock", description="Lock a channel") -# @slash_option(name="reason", -# description="Lock Reason", -# opt_type=3, -# required=True,) -# @slash_option(name="duration", -# description="Lock duration in minutes (default 10)", -# opt_type=4, -# required=False,) -# @slash_option(name="channel", -# description="Channel to lock", -# opt_type=7, -# required=False,) -# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) -# async def _lock( -# self, -# ctx: InteractionContext, -# reason: str, -# duration: int = 10, -# channel: Union[GuildText, GuildVoice] = None, -# ) -> None: -# await ctx.defer(ephemeral=True) -# if duration <= 0: -# await ctx.send("Duration must be > 0", ephemeral=True) -# return -# -# elif duration > 60 * 12: -# await ctx.send("Duration must be <= 12 hours", ephemeral=True) -# return -# -# if len(reason) > 100: -# await ctx.send("Reason must be <= 100 characters", ephemeral=True) -# return -# if not channel: -# channel = ctx.channel -# -# # role = ctx.guild.default_role # Uncomment once implemented -# if isinstance(channel, GuildText): -# to_deny = Permissions.SEND_MESSAGES -# elif isinstance(channel, GuildVoice): -# to_deny = Permissions.CONNECT | Permissions.SPEAK -# -# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny) -# # TODO: Get original permissions -# # TODO: Apply overwrite -# overwrite = overwrite -# _ = Lock( -# channel=channel.id, -# guild=ctx.guild.id, -# admin=ctx.author.id, -# reason=reason, -# duration=duration, -# ) # .save() # Uncomment once implemented -# # await ctx.send(f"{channel.mention} locked for {duration} minute(s)") -# await ctx.send("Unfortunately, this is not yet implemented", hidden=True) -# -# @cog_ext.cog_slash( -# name="unlock", -# description="Unlocks a channel", -# choices=[ -# create_option( -# name="channel", -# description="Channel to lock", -# opt_type=7, -# required=False, -# ), -# ], -# ) -# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) -# async def _unlock( -# self, -# ctx: InteractionContext, -# channel: Union[GuildText, GuildVoice] = None, -# ) -> None: -# if not channel: -# channel = ctx.channel -# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first() -# if not lock: -# await ctx.send(f"{channel.mention} not locked.", ephemeral=True) -# return -# for role in ctx.guild.roles: -# with suppress(Exception): -# await self._unlock_channel(channel, role, ctx.author) -# lock.active = False -# lock.save() -# await ctx.send(f"{channel.mention} unlocked") +from typing import Union + +from dis_snek import InteractionContext, Scale +from dis_snek.client.utils.misc_utils import get +from dis_snek.models.discord.channel import GuildText, GuildVoice +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Permission + +from jarvis.utils.permissions import admin_or_permissions + + +class LockCog(Scale): + """J.A.R.V.I.S. LockCog.""" + + @slash_command(name="lock", description="Lock a channel") + @slash_option( + name="reason", + description="Lock Reason", + opt_type=3, + required=True, + ) + @slash_option( + name="duration", + description="Lock duration in minutes (default 10)", + opt_type=4, + required=False, + ) + @slash_option( + name="channel", + description="Channel to lock", + opt_type=7, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) + async def _lock( + self, + ctx: InteractionContext, + reason: str, + duration: int = 10, + channel: Union[GuildText, GuildVoice] = None, + ) -> None: + await ctx.defer(ephemeral=True) + if duration <= 0: + await ctx.send("Duration must be > 0", ephemeral=True) + return + + elif duration > 60 * 12: + await ctx.send("Duration must be <= 12 hours", ephemeral=True) + return + + if len(reason) > 100: + await ctx.send("Reason must be <= 100 characters", ephemeral=True) + return + if not channel: + channel = ctx.channel + + # role = ctx.guild.default_role # Uncomment once implemented + if isinstance(channel, GuildText): + to_deny = Permissions.SEND_MESSAGES + elif isinstance(channel, GuildVoice): + to_deny = Permissions.CONNECT | Permissions.SPEAK + + current = get(channel.permission_overwrites, id=ctx.guild.id) + if current: + current = Permission(id=ctx.guild.id, allow=int(current.allow), deny=int(current.deny)) + role = await ctx.guild.fetch_role(ctx.guild.id) + + await channel.add_permission(target=role, deny=to_deny, reason="Locked") + await Lock( + channel=channel.id, + guild=ctx.guild.id, + admin=ctx.author.id, + reason=reason, + duration=duration, + original_perms=current, + ).commit() + await ctx.send(f"{channel.mention} locked for {duration} minute(s)") + + @slash_command(name="unlock", description="Unlock a channel") + @slash_option( + name="channel", + description="Channel to unlock", + opt_type=OptionTypes.CHANNEL, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) + async def _unlock( + self, + ctx: InteractionContext, + channel: Union[GuildText, GuildVoice] = None, + ) -> None: + if not channel: + channel = ctx.channel + lock = await Lock.find_one(q(guild=ctx.guild.id, channel=channel.id, active=True)) + if not lock: + await ctx.send(f"{channel.mention} not locked.", ephemeral=True) + return + + overwrite = get(channel.permission_overwrites, id=ctx.guild.id) + if overwrite and lock.original_perms: + overwrite.allow = lock.original_perms.allow + overwrite.deny = lock.original_perms.deny + await channel.edit_permission(overwrite, reason="Unlock") + elif overwrite and not lock.original_perms: + await channel.delete_permission(target=overwrite, reason="Unlock") + + lock.active = False + await lock.commit() + await ctx.send(f"{channel.mention} unlocked") diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index cd711fb..14407d8 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,101 +1,159 @@ """J.A.R.V.I.S. LockdownCog.""" -from contextlib import suppress -from datetime import datetime +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.client.utils.misc_utils import find_all, get +from dis_snek.models.discord.channel import GuildCategory, GuildChannel +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.discord.guild import Guild +from dis_snek.models.discord.user import Member +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Lockdown, Permission -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option - -from jarvis.db.models import Lock -from jarvis.utils.cachecog import CacheCog - -# from jarvis.utils.permissions import admin_or_permissions +from jarvis.utils.permissions import admin_or_permissions -class LockdownCog(CacheCog): +async def lock(bot: Snake, target: GuildChannel, admin: Member, reason: str, duration: int) -> None: + """ + Lock an existing channel + + Args: + bot: Bot instance + target: Target channel + admin: Admin who initiated lockdown + """ + to_deny = Permissions.SEND_MESSAGES | Permissions.CONNECT | Permissions.SPEAK + current = get(target.permission_overwrites, id=target.guild.id) + if current: + current = Permission(id=target.guild.id, allow=int(current.allow), deny=int(current.deny)) + role = await target.guild.fetch_role(target.guild.id) + await target.add_permission(target=role, deny=to_deny, reason="Lockdown") + await Lock( + channel=target.id, + guild=target.guild.id, + admin=admin.id, + reason=reason, + duration=duration, + original_perms=current, + ).commit() + + +async def lock_all(bot: Snake, guild: Guild, admin: Member, reason: str, duration: int) -> None: + """ + Lock all channels + + Args: + bot: Bot instance + guild: Target guild + admin: Admin who initiated lockdown + """ + role = await guild.fetch_role(guild.id) + categories = find_all(lambda x: isinstance(x, GuildCategory), guild.channels) + for category in categories: + await lock(bot, category, admin, reason, duration) + perms = category.permissions_for(role) + + for channel in category.channels: + if perms != channel.permissions_for(role): + await lock(bot, channel, admin, reason, duration) + + +async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: + """ + Unlock all locked channels + + Args: + bot: Bot instance + target: Target channel + admin: Admin who ended lockdown + """ + locks = Lock.find(q(guild=guild.id, active=True)) + async for lock in locks: + target = await guild.fetch_channel(lock.channel) + if target: + overwrite = get(target.permission_overwrites, id=guild.id) + if overwrite and lock.original_perms: + overwrite.allow = lock.original_perms.allow + overwrite.deny = lock.original_perms.deny + await target.edit_permission(overwrite, reason="Lockdown end") + elif overwrite and not lock.original_perms: + await target.delete_permission(target=overwrite, reason="Lockdown end") + lock.active = False + await lock.commit() + lockdown = await Lockdown.find_one(q(guild=guild.id, active=True)) + if lockdown: + lockdown.active = False + await lockdown.commit() + + +class LockdownCog(Scale): """J.A.R.V.I.S. LockdownCog.""" - def __init__(self, bot: commands.Bot): - super().__init__(bot) - - @cog_ext.cog_subcommand( - base="lockdown", - name="start", - description="Locks a server", - choices=[ - create_option( - name="reason", - description="Lockdown Reason", - opt_type=3, - required=True, - ), - create_option( - name="duration", - description="Lockdown duration in minutes (default 10)", - opt_type=4, - required=False, - ), - ], + @slash_command( + name="lockdown", + description="Manage server-wide lockdown", + sub_cmd_name="start", + sub_cmd_description="Lockdown the server", ) - # @check(admin_or_permissions(manage_channels=True)) + @slash_option( + name="reason", description="Lockdown reason", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="duration", + description="Duration in minutes", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_start( self, - ctx: SlashContext, + ctx: InteractionContext, reason: str, duration: int = 10, ) -> None: - await ctx.defer(ephemeral=True) + await ctx.defer() if duration <= 0: await ctx.send("Duration must be > 0", ephemeral=True) return elif duration >= 300: await ctx.send("Duration must be < 5 hours", ephemeral=True) return - channels = ctx.guild.channels - roles = ctx.guild.roles - updates = [] - for channel in channels: - for role in roles: - with suppress(Exception): - await self._lock_channel(channel, role, ctx.author, reason) - updates.append( - Lock( - channel=channel.id, - guild=ctx.guild.id, - admin=ctx.author.id, - reason=reason, - duration=duration, - active=True, - created_at=datetime.utcnow(), - ) - ) - if updates: - Lock.objects().insert(updates) - await ctx.send(f"Server locked for {duration} minute(s)") - @cog_ext.cog_subcommand( - base="lockdown", - name="end", - description="Unlocks a server", - ) - @commands.has_permissions(administrator=True) + exists = await Lockdown.find_one(q(guild=ctx.guild.id, active=True)) + if exists: + await ctx.send("Server already in lockdown", ephemeral=True) + return + + await lock_all(self.bot, ctx.guild, ctx.author, reason, duration) + role = await ctx.guild.fetch_role(ctx.guild.id) + original_perms = role.permissions + new_perms = role.permissions & ~Permissions.SEND_MESSAGES + await role.edit(permissions=new_perms) + await Lockdown( + admin=ctx.author.id, + duration=duration, + guild=ctx.guild.id, + reason=reason, + original_perms=int(original_perms), + ).commit() + await ctx.send("Server now in lockdown.") + + @slash_command(name="lockdown", sub_cmd_name="end", sub_cmd_description="End a lockdown") + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_end( self, - ctx: SlashContext, + ctx: InteractionContext, ) -> None: - channels = ctx.guild.channels - roles = ctx.guild.roles - update = False - locks = Lock.objects(guild=ctx.guild.id, active=True) - if not locks: - await ctx.send("No lockdown detected.", ephemeral=True) - return await ctx.defer() - for channel in channels: - for role in roles: - with suppress(Exception): - await self._unlock_channel(channel, role, ctx.author) - update = True - if update: - Lock.objects(guild=ctx.guild.id, active=True).update(set__active=False) - await ctx.send("Server unlocked") + + lockdown = await Lockdown.find_one(q(guild=ctx.guild.id, active=True)) + if not lockdown: + await ctx.send("Server not in lockdown", ephemeral=True) + return + + await unlock_all(self.bot, ctx.guild, ctx.author) + await ctx.send("Server no longer in lockdown.") From 2308342de767dc98c552879f77287b24e58b6a91 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:24:53 -0600 Subject: [PATCH 06/95] Create ModlogCase scale wrapper --- jarvis/cogs/admin/__init__.py | 6 +-- jarvis/cogs/admin/ban.py | 11 ++++- jarvis/cogs/admin/kick.py | 5 ++- jarvis/cogs/admin/mute.py | 8 ++-- jarvis/cogs/admin/warning.py | 5 ++- jarvis/utils/cachecog.py | 35 ---------------- jarvis/utils/cogs.py | 75 +++++++++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 49 deletions(-) delete mode 100644 jarvis/utils/cachecog.py create mode 100644 jarvis/utils/cogs.py diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index b11ccd4..daaca31 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,15 +1,15 @@ """J.A.R.V.I.S. Admin Cogs.""" from dis_snek import Snake -from jarvis.cogs.admin import ban, kick, mute, purge, roleping, warning +from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning def setup(bot: Snake) -> None: """Add admin cogs to J.A.R.V.I.S.""" ban.BanCog(bot) kick.KickCog(bot) - # lock.LockCog(bot) - # lockdown.LockdownCog(bot) + lock.LockCog(bot) + lockdown.LockdownCog(bot) mute.MuteCog(bot) purge.PurgeCog(bot) roleping.RolepingCog(bot) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index f4742cb..9b98ec9 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. BanCog.""" import re -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -17,10 +17,11 @@ from jarvis_core.db import q from jarvis_core.db.models import Ban, Unban from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class BanCog(Scale): +class BanCog(ModcaseCog): """J.A.R.V.I.S. BanCog.""" async def discord_apply_ban( @@ -105,6 +106,12 @@ class BanCog(Scale): SlashCommandChoice(name="Soft", value="soft"), ], ) + @slash_option( + name="duration", + description="Temp ban duration in hours", + opt_type=OptionTypes.INTEGER, + required=False, + ) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _ban( self, diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 3bb7cfb..8a3a6b1 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. KickCog.""" -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( @@ -11,10 +11,11 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Kick from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class KickCog(Scale): +class KickCog(ModcaseCog): """J.A.R.V.I.S. KickCog.""" @slash_command(name="kick", description="Kick a user") diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 5ac5ec5..ffb634f 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. MuteCog.""" from datetime import datetime -from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( @@ -14,15 +14,13 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Mute from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class MuteCog(Scale): +class MuteCog(ModcaseCog): """J.A.R.V.I.S. MuteCog.""" - def __init__(self, bot: Snake): - self.bot = bot - @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 7e60987..0ef4d62 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -14,11 +14,12 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Warning from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.embeds import warning_embed from jarvis.utils.permissions import admin_or_permissions -class WarningCog(Scale): +class WarningCog(ModcaseCog): """J.A.R.V.I.S. WarningCog.""" @slash_command(name="warn", description="Warn a user") diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py deleted file mode 100644 index d7be74b..0000000 --- a/jarvis/utils/cachecog.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Cog wrapper for command caching.""" -from datetime import datetime, timedelta - -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import find -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger - - -class CacheCog(Scale): - """Cog wrapper for command caching.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.cache = {} - self._expire_interaction.start() - - def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: - """Check the cache.""" - if not kwargs: - kwargs = {} - return find( - lambda x: x["command"] == ctx.subcommand_name # noqa: W503 - and x["user"] == ctx.author.id # noqa: W503 - and x["guild"] == ctx.guild.id # noqa: W503 - and all(x[k] == v for k, v in kwargs.items()), # noqa: W503 - self.cache.values(), - ) - - @Task.create(IntervalTrigger(minutes=1)) - async def _expire_interaction(self) -> None: - keys = list(self.cache.keys()) - for key in keys: - if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): - del self.cache[key] diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py new file mode 100644 index 0000000..f9361c3 --- /dev/null +++ b/jarvis/utils/cogs.py @@ -0,0 +1,75 @@ +"""Cog wrapper for command caching.""" +from datetime import datetime, timedelta + +from dis_snek import Context, Scale, Snake +from dis_snek.client.utils.misc_utils import find +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Action, Ban, Kick, Modlog, Mute, Note, Warning + +MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} + + +class CacheCog(Scale): + """Cog wrapper for command caching.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.cache = {} + self._expire_interaction.start() + + def check_cache(self, ctx: Context, **kwargs: dict) -> dict: + """Check the cache.""" + if not kwargs: + kwargs = {} + return find( + lambda x: x["command"] == ctx.subcommand_name # noqa: W503 + and x["user"] == ctx.author.id # noqa: W503 + and x["guild"] == ctx.guild.id # noqa: W503 + and all(x[k] == v for k, v in kwargs.items()), # noqa: W503 + self.cache.values(), + ) + + @Task.create(IntervalTrigger(minutes=1)) + async def _expire_interaction(self) -> None: + keys = list(self.cache.keys()) + for key in keys: + if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): + del self.cache[key] + + +class ModcaseCog(Scale): + """Cog wrapper for moderation case logging.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.add_scale_postrun(self.log) + + async def log(self, ctx: Context, *args: list, **kwargs: dict) -> None: + """ + Log a moderation activity in a moderation case. + + Args: + ctx: Command context + """ + name = self.__name__.replace("Cog", "") + + if name not in ["Lock", "Lockdown", "Purge", "Roleping"]: + user = kwargs.pop("user", None) + if not user: + # Log warning about missing user + return + coll = MODLOG_LOOKUP.get(name, None) + if not coll: + # Log warning about unsupported action + return + + action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) + if not action: + # Log warning about missing action + return + + action = Action(action_type=name.lower(), parent=action.id) + note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") + await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() From d3449fc5ed5cd84a33b80760a18bb048c63065af Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:25:30 -0600 Subject: [PATCH 07/95] Allow giving credit to other users for GitLab issues --- jarvis/cogs/gl.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index cdd08a4..f8dbdfd 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -7,6 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles +from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -422,11 +423,16 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", sub_cmd_name="report", sub_cmd_description="Report an issue", scopes=guild_ids + @slash_command(name="issue", description="Report an issue on GitLab", scopes=guild_ids) + @slash_option( + name="user", + description="Credit someone else for this issue", + opt_type=OptionTypes.USER, + required=False, ) @cooldown(bucket=Buckets.USER, rate=1, interval=600) - async def _open_issue(self, ctx: InteractionContext) -> None: + async def _open_issue(self, ctx: InteractionContext, user: Member = None) -> None: + user = user or ctx.author modal = Modal( title="Open a new issue on GitLab", components=[ @@ -454,7 +460,7 @@ class GitlabCog(Scale): return if not title.startswith("[Discord]"): title = "[Discord] " + title - desc = f"Opened by `@{ctx.author.username}` on Discord\n\n" + desc + desc = f"Opened by `@{user.username}` on Discord\n\n" + desc issue = self.project.issues.create(data={"title": title, "description": desc}) embed = build_embed( title=f"Issue #{issue.id} Created", From e59e566f30a73749a8a3554b8808387bba29a04d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:25:47 -0600 Subject: [PATCH 08/95] Fix reminders, use timestamps --- jarvis/cogs/remindme.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index e53a532..002964e 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -113,7 +113,7 @@ class RemindmeCog(Scale): ) return - remind_at = datetime.now() + timedelta(**delta) + remind_at = datetime.utcnow() + timedelta(**delta) r = Reminder( user=ctx.author.id, @@ -134,7 +134,7 @@ class RemindmeCog(Scale): EmbedField(name="Message", value=message), EmbedField( name="When", - value=remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=f"", inline=False, ), ], @@ -157,7 +157,7 @@ class RemindmeCog(Scale): if reminder.private and isinstance(ctx.channel, GuildChannel): fields.embed( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value="Please DM me this command to view the content of this reminder", inline=False, ) @@ -165,7 +165,7 @@ class RemindmeCog(Scale): else: fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value=f"{reminder.message}\n\u200b", inline=False, ) @@ -187,15 +187,7 @@ class RemindmeCog(Scale): @slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders") async def _list(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return - reminders = await Reminder.find(q(user=ctx.author.id, active=True)) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: await ctx.send("You have no reminders set.", ephemeral=True) return @@ -206,7 +198,7 @@ class RemindmeCog(Scale): @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: - reminders = await Reminder.find(q(user=ctx.author.id, active=True)) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: await ctx.send("You have no reminders set", ephemeral=True) return @@ -214,7 +206,7 @@ class RemindmeCog(Scale): options = [] for reminder in reminders: option = SelectOption( - label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + label=f"", value=str(reminder.id), emoji="⏰", ) @@ -249,7 +241,7 @@ class RemindmeCog(Scale): if reminder.private and isinstance(ctx.channel, GuildChannel): fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value="Private reminder", inline=False, ) @@ -257,7 +249,7 @@ class RemindmeCog(Scale): else: fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value=reminder.message, inline=False, ) From 32841b230aa6258fd6c0462988938485239c9842 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:26:24 -0600 Subject: [PATCH 09/95] More work on settings, WIP --- jarvis/cogs/settings.py | 47 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index c197573..de39409 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,9 +1,16 @@ """J.A.R.V.I.S. Settings Management Cog.""" from typing import Any +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) from dis_snek.models.snek.command import check from discord import Role, TextChannel -from discord.ext import commands from discord.utils import find from discord_slash import SlashContext, cog_ext from discord_slash.utils.manage_commands import create_option @@ -15,12 +22,9 @@ from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions -class SettingsCog(commands.Cog): +class SettingsCog(Scale): """J.A.R.V.I.S. Settings Management Cog.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" existing = await Setting.find_one(q(setting=setting, guild=guild)) @@ -38,24 +42,19 @@ class SettingsCog(commands.Cog): return await existing.delete() return False - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="modlog", - description="Set modlog channel", - choices=[ - create_option( - name="channel", - description="Modlog channel", - opt_type=7, - required=True, - ) - ], + @slash_command( + name="settings", + description="Control bot settings", + sub_cmd_name="modlog", + sub_cmd_description="Set ActivityLog channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", ephemeral=True) + @slash_option( + name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_modlog(self, ctx: InteractionContext(), channel: GuildText) -> None: + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText", ephemeral=True) return self.update_settings("modlog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New modlog channel is {channel.mention}") @@ -74,7 +73,7 @@ class SettingsCog(commands.Cog): ) ], ) - @check(admin_or_permissions(manage_guild=True)) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None: if not isinstance(channel, TextChannel): await ctx.send("Channel must be a TextChannel", ephemeral=True) @@ -261,6 +260,6 @@ class SettingsCog(commands.Cog): await ctx.send(f"Guild settings cleared: `{deleted is not None}`") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add SettingsCog to J.A.R.V.I.S.""" SettingsCog(bot) From dc5919ff869009c975eca3390a129710e704562c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:30:14 -0600 Subject: [PATCH 10/95] Update config --- config.example.yaml | 44 +++++++++++++++++++++++++++----------------- jarvis/config.py | 6 +----- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index cfa17f6..6aa129a 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,24 +1,34 @@ --- - token: api key here - client_id: 123456789012345678 - logo: alligator2 + token: bot token + twitter: + consumer_key: key + consumer_secret: secret + access_token: access token + access_secret: access secret mongo: connect: - username: user - password: pass - host: localhost + username: username + password: password + host: hostname port: 27017 database: database urls: - url_name: url - url_name2: url2 - max_messages: 1000 - gitlab_token: null + extra: urls + max_messages: 10000 + gitlab_token: token cogs: - - list - - of - - enabled - - cogs - - all - - if - - empty + - admin + - autoreact + - dev + - image + - gl + - remindme + - rolegiver + # - settings + - starboard + - twitter + - util + - verify + log_level: INFO + sync: false + #sync_commands: True diff --git a/jarvis/config.py b/jarvis/config.py index e8d65bd..3ab039e 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -12,7 +12,7 @@ except ImportError: class JarvisConfig(CConfig): - REQUIRED = ["token", "client_id", "mongo", "urls"] + REQUIRED = ["token", "mongo", "urls"] OPTIONAL = { "sync": False, "log_level": "WARNING", @@ -39,8 +39,6 @@ class Config(object): def init( self, token: str, - client_id: str, - logo: str, mongo: dict, urls: dict, sync: bool = False, @@ -53,8 +51,6 @@ class Config(object): ) -> None: """Initialize the config object.""" self.token = token - self.client_id = client_id - self.logo = logo self.mongo = mongo self.urls = urls self.log_level = log_level From be6d88449ee867405a25ae768b8d24ba3738628a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:30:45 -0600 Subject: [PATCH 11/95] Fix verification --- jarvis/cogs/verify.py | 66 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 2255623..a4b0647 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -3,8 +3,8 @@ import asyncio from random import randint from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.application_commands import slash_command from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows +from dis_snek.models.snek.application_commands import slash_command from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -41,10 +41,10 @@ class VerifyCog(Scale): await ctx.defer() role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) if not role: - await ctx.send("This guild has not enabled verification", delete_after=5) + message = await ctx.send("This guild has not enabled verification", ephemeral=True) return - if await ctx.guild.get_role(role.value) in ctx.author.roles: - await ctx.send("You are already verified.", delete_after=5) + if await ctx.guild.fetch_role(role.value) in ctx.author.roles: + await ctx.send("You are already verified.", ephemeral=True) return components = create_layout() message = await ctx.send( @@ -53,35 +53,39 @@ class VerifyCog(Scale): ) try: - context = await self.bot.wait_for_component( - messages=message, check=lambda x: ctx.author.id == x.author.id, timeout=30 - ) - - correct = context.context.custom_id.split("||")[-1] == "yes" - if correct: - for row in components: - for component in row.components: - component.disabled = True - setting = await Setting.find_one(guild=ctx.guild.id, setting="verified") - role = await ctx.guild.get_role(setting.value) - await ctx.author.add_roles(role, reason="Verification passed") - setting = await Setting.find_one(guild=ctx.guild.id, setting="unverified") - if setting: - role = await ctx.guild.get_role(setting.value) - await ctx.author.remove_roles(role, reason="Verification passed") - - await context.context.edit_origin( - content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", - components=components, + verified = False + while not verified: + response = await self.bot.wait_for_component( + messages=message, + check=lambda x: ctx.author.id == x.context.author.id, + timeout=30, ) - await context.context.message.delete(delay=5) - else: - await context.context.edit_origin( - content=( - f"{ctx.author.mention}, incorrect. " - "Please press the button that says `YES`" + + correct = response.context.custom_id.split("||")[-1] == "yes" + if correct: + for row in components: + for component in row.components: + component.disabled = True + setting = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.add_role(role, reason="Verification passed") + setting = await Setting.find_one(q(guild=ctx.guild.id, setting="unverified")) + if setting: + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.remove_role(role, reason="Verification passed") + + await response.context.edit_origin( + content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", + components=components, + ) + await response.context.message.delete(delay=5) + else: + await response.context.edit_origin( + content=( + f"{ctx.author.mention}, incorrect. " + "Please press the button that says `YES`" + ) ) - ) except asyncio.TimeoutError: await message.delete(delay=30) From 75b850e73465c295654009710553b055831e8a8a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:55:51 -0600 Subject: [PATCH 12/95] Replace all custom booleans with standard booleans --- jarvis/cogs/admin/ban.py | 8 ++++---- jarvis/cogs/admin/warning.py | 11 ++--------- jarvis/cogs/remindme.py | 10 ++-------- jarvis/cogs/settings.py | 8 ++++---- jarvis/cogs/twitter.py | 19 ++++--------------- 5 files changed, 16 insertions(+), 40 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 9b98ec9..c0c8050 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -302,13 +302,13 @@ class BanCog(ModcaseCog): @slash_option( name="active", description="Active bans", - opt_type=OptionTypes.INTEGER, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], ) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) - async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: - active = bool(active) + async def _bans_list( + self, ctx: InteractionContext, btype: int = 0, active: bool = True + ) -> None: types = [0, "perm", "temp", "soft"] search = {"guild": ctx.guild.id} if active: diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 0ef4d62..86b08aa 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -6,7 +6,6 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -66,17 +65,11 @@ class WarningCog(ModcaseCog): @slash_option( name="active", description="View active only", - opt_type=OptionTypes.INTEGER, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value=1), - SlashCommandChoice(name="No", value=0), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: - active = bool(active) - + async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: warnings = ( await Warning.find( user=user.id, diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 002964e..1b36042 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -13,7 +13,6 @@ from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -37,19 +36,14 @@ class RemindmeCog(Scale): @slash_option( name="private", description="Send as DM?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="y"), - SlashCommandChoice(name="No", value="n"), - ], ) async def _remindme( self, ctx: InteractionContext, - private: str = "n", + private: bool = False, ) -> None: - private = private == "y" modal = Modal( title="Set your reminder!", components=[ diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index de39409..fec3ee6 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -150,16 +150,16 @@ class SettingsCog(Scale): create_option( name="active", description="Active?", - opt_type=4, + opt_type=OptionTypes.BOOLEAN, required=True, ) ], ) @check(admin_or_permissions(manage_guild=True)) - async def _set_invitedel(self, ctx: SlashContext, active: int) -> None: + async def _set_invitedel(self, ctx: SlashContext, active: bool) -> None: await ctx.defer() - self.update_settings("noinvite", bool(active), ctx.guild.id) - await ctx.send(f"Settings applied. Automatic invite active: {bool(active)}") + self.update_settings("noinvite", active, ctx.guild.id) + await ctx.send(f"Settings applied. Automatic invite active: {active}") @cog_ext.cog_subcommand( base="settings", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 836b57b..79521a0 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -8,7 +8,6 @@ from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -51,19 +50,14 @@ class TwitterCog(Scale): @slash_option( name="retweets", description="Mirror re-tweets?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="Yes"), - SlashCommandChoice(name="No", value="No"), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_follow( - self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes" + self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: bool = True ) -> None: handle = handle.lower() - retweets = retweets == "Yes" if len(handle) > 15: await ctx.send("Invalid Twitter handle", ephemeral=True) return @@ -176,16 +170,11 @@ class TwitterCog(Scale): @slash_option( name="retweets", description="Mirror re-tweets?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="Yes"), - SlashCommandChoice(name="No", value="No"), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: - retweets = retweets == "Yes" + async def _twitter_modify(self, ctx: InteractionContext, retweets: bool = True) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) twitters = [] async for twitter in t: From b0c461ab7d25393840c23a832b2373c4be4193fa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 18:27:23 -0600 Subject: [PATCH 13/95] Finally add logging, use dateparser in RemindMe --- .flake8 | 1 + jarvis/__init__.py | 21 +++- jarvis/client.py | 19 +++- jarvis/cogs/admin/ban.py | 7 +- jarvis/cogs/admin/kick.py | 8 +- jarvis/cogs/admin/lock.py | 7 +- jarvis/cogs/admin/lockdown.py | 6 + jarvis/cogs/admin/mute.py | 7 +- jarvis/cogs/admin/purge.py | 3 + jarvis/cogs/admin/roleping.py | 8 +- jarvis/cogs/admin/warning.py | 19 +++- jarvis/cogs/autoreact.py | 2 + jarvis/cogs/ctc2.py | 28 +---- jarvis/cogs/dbrand.py | 2 + jarvis/cogs/dev.py | 5 + jarvis/cogs/gl.py | 8 +- jarvis/cogs/image.py | 2 + jarvis/cogs/remindme.py | 52 ++++----- jarvis/cogs/rolegiver.py | 2 + jarvis/cogs/settings.py | 5 + jarvis/cogs/starboard.py | 3 + jarvis/cogs/twitter.py | 6 +- jarvis/cogs/util.py | 4 +- jarvis/cogs/verify.py | 2 + poetry.lock | 199 +++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 26 files changed, 349 insertions(+), 78 deletions(-) diff --git a/.flake8 b/.flake8 index 6e7cd96..50c8d97 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] extend-ignore = Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually + ANN002, ANN003, # Ignore *args, **kwargs ANN1, # Ignore self and cls annotations ANN204, ANN206, # return annotations for special methods and class methods D105, D107, # Missing Docstrings in magic method and __init__ diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 5c7e26c..425519e 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -4,22 +4,25 @@ from importlib.metadata import version as _v from dis_snek import Intents from jarvis_core.db import connect +from jarvis_core.log import get_logger from jarvis import utils from jarvis.client import Jarvis -from jarvis.config import get_config +from jarvis.config import JarvisConfig try: __version__ = _v("jarvis") except Exception: __version__ = "0.0.0" -jconfig = get_config() +jconfig = JarvisConfig.from_yaml() -logger = logging.getLogger("discord") -logger.setLevel(logging.getLevelName(jconfig.log_level)) +logger = get_logger("jarvis") +logger.setLevel(jconfig.log_level) file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") -file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) +file_handler.setFormatter( + logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s") +) logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES @@ -31,11 +34,17 @@ jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.s async def run() -> None: """Run J.A.R.V.I.S.""" + logger.info("Starting JARVIS") + logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") - jconfig.get_db_config() + logger.debug("Loading configuration from database") + # jconfig.get_db_config() + logger.debug("Loading extensions") for extension in utils.get_extensions(): jarvis.load_extension(extension) + logger.debug(f"Loaded {extension}") jarvis.max_messages = jconfig.max_messages + logger.debug("Running JARVIS") await jarvis.astart(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index c0ebc81..74e6020 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,4 +1,5 @@ """Custom JARVIS client.""" +import logging import re import traceback from datetime import datetime @@ -49,15 +50,19 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 super().__init__(*args, **kwargs) + self.logger = logging.getLogger(__name__) self.phishing_domains = [] @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: + self.logger.debug("Updating phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/recent/86415") response.raise_for_status() data = await response.json() + self.logger.debug(f"Found {len(data)} changes to phishing domains") + for update in data: if update["type"] == "add": if update["domain"] not in self.phishing_domains: @@ -67,19 +72,21 @@ class Jarvis(Snake): self.phishing_domains.remove(update["domain"]) async def _sync_domains(self) -> None: + self.logger.debug("Loading phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/all") response.raise_for_status() self.phishing_domains = await response.json() + self.logger.info(f"Protected from {len(self.phishing_domains)} phishing domains") @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" await self._sync_domains() self._update_domains.start() - print("Logged in as {}".format(self.user)) # noqa: T001 - print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 - print( # noqa: T001 + self.logger.info("Logged in as {}".format(self.user)) # noqa: T001 + self.logger.info("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 + self.logger.info( # noqa: T001 "https://discord.com/api/oauth2/authorize?client_id=" "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) @@ -418,7 +425,11 @@ class Jarvis(Snake): message = event.message modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) if modlog: - fields = [EmbedField("Original Message", message.content or "N/A", False)] + try: + content = message.content or "N/A" + except AttributeError: + content = "N/A" + fields = [EmbedField("Original Message", content, False)] if message.attachments: value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index c0c8050..6ca8447 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. BanCog.""" +import logging import re -from dis_snek import InteractionContext, Permissions +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -24,6 +25,10 @@ from jarvis.utils.permissions import admin_or_permissions class BanCog(ModcaseCog): """J.A.R.V.I.S. BanCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + async def discord_apply_ban( self, ctx: InteractionContext, diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 8a3a6b1..e00f096 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. KickCog.""" -from dis_snek import InteractionContext, Permissions +import logging + +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( @@ -18,6 +20,10 @@ from jarvis.utils.permissions import admin_or_permissions class KickCog(ModcaseCog): """J.A.R.V.I.S. KickCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="kick", description="Kick a user") @slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index eefdada..e1aeb61 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. LockCog.""" +import logging from typing import Union -from dis_snek import InteractionContext, Scale +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildText, GuildVoice from dis_snek.models.discord.enums import Permissions @@ -20,6 +21,10 @@ from jarvis.utils.permissions import admin_or_permissions class LockCog(Scale): """J.A.R.V.I.S. LockCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="lock", description="Lock a channel") @slash_option( name="reason", diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index 14407d8..e55b39e 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. LockdownCog.""" +import logging + from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import find_all, get from dis_snek.models.discord.channel import GuildCategory, GuildChannel @@ -93,6 +95,10 @@ async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: class LockdownCog(Scale): """J.A.R.V.I.S. LockdownCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command( name="lockdown", description="Manage server-wide lockdown", diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index ffb634f..a8c7666 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. MuteCog.""" +import logging from datetime import datetime -from dis_snek import InteractionContext, Permissions +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( @@ -21,6 +22,10 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): """J.A.R.V.I.S. MuteCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 91d7caa..b8d6f4a 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. PurgeCog.""" +import logging + from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( @@ -18,6 +20,7 @@ class PurgeCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command(name="purge", description="Purge messages from channel") @slash_option( diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 38a3c87..9ef8152 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. RolepingCog.""" -from dis_snek import InteractionContext, Permissions, Scale +import logging + +from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -21,6 +23,10 @@ from jarvis.utils.permissions import admin_or_permissions class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command( name="roleping", description="Set up warnings for pinging specific roles", diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 86b08aa..3ef1a8a 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions +import logging + +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -10,6 +12,7 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q from jarvis_core.db.models import Warning from jarvis.utils import build_embed @@ -21,6 +24,10 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(ModcaseCog): """J.A.R.V.I.S. WarningCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="warn", description="Warn a user") @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -72,8 +79,10 @@ class WarningCog(ModcaseCog): async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: warnings = ( await Warning.find( - user=user.id, - guild=ctx.guild.id, + q( + user=user.id, + guild=ctx.guild.id, + ) ) .sort("created_at", -1) .to_list(None) @@ -94,7 +103,7 @@ class WarningCog(ModcaseCog): else: fields = [] for warn in active_warns: - admin = await ctx.guild.get_member(warn.admin) + admin = await ctx.guild.fetch_member(warn.admin) admin_name = "||`[redacted]`||" if admin: admin_name = f"{admin.username}#{admin.discriminator}" @@ -144,6 +153,6 @@ class WarningCog(ModcaseCog): embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) - paginator = Paginator(bot=self.bot, *pages, timeout=300) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) await paginator.send(ctx) diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index b5caa19..40a5012 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Autoreact Cog.""" +import logging import re from typing import Optional, Tuple @@ -23,6 +24,7 @@ class AutoReactCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") async def create_autoreact( diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 67f9d20..8d4e71b 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -1,9 +1,9 @@ """J.A.R.V.I.S. Complete the Code 2 Cog.""" +import logging import re -from datetime import datetime, timedelta import aiohttp -from dis_snek import InteractionContext, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User @@ -14,7 +14,6 @@ from jarvis_core.db import q from jarvis_core.db.models import Guess from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] @@ -25,11 +24,12 @@ invites = re.compile( ) -class CTCCog(CacheCog): +class CTCCog(Scale): """J.A.R.V.I.S. Complete the Code 2 Cog.""" def __init__(self, bot: Snake): - super().__init__(bot) + self.bot = bot + self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() self.url = "https://completethecodetwo.cards/pw" @@ -79,6 +79,7 @@ class CTCCog(CacheCog): if guessed: await ctx.send("Already guessed, dipshit.", ephemeral=True) return + result = await self._session.post(self.url, data=guess) correct = False if 200 <= result.status < 400: @@ -96,15 +97,6 @@ class CTCCog(CacheCog): ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return - guesses = Guess.objects().order_by("-correct", "-id") fields = [] for guess in guesses: @@ -141,14 +133,6 @@ class CTCCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "guild": ctx.guild.id, - "user": ctx.author.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 9a14251..761ff12 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. dbrand cog.""" +import logging import re import aiohttp @@ -28,6 +29,7 @@ class DbrandCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self.base_url = "https://dbrand.com/" self._session = aiohttp.ClientSession() self._session.headers.update({"Content-Type": "application/json"}) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f867100..5d419e3 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -1,6 +1,7 @@ """J.A.R.V.I.S. Developer Cog.""" import base64 import hashlib +import logging import re import subprocess # noqa: S404 import uuid as uuidpy @@ -47,6 +48,10 @@ MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): """J.A.R.V.I.S. Developer Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="hash", description="Hash some data") @slash_option( name="method", diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index f8dbdfd..6bb92d0 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. GitLab Cog.""" import asyncio +import logging from datetime import datetime import gitlab @@ -17,7 +18,7 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets -from jarvis.config import get_config +from jarvis.config import JarvisConfig from jarvis.utils import build_embed guild_ids = [862402786116763668] @@ -28,7 +29,8 @@ class GitlabCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = get_config() + self.logger = logging.getLogger(__name__) + config = JarvisConfig.from_yaml() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) # J.A.R.V.I.S. GitLab ID is 29 self.project = self._gitlab.projects.get(29) @@ -473,5 +475,5 @@ class GitlabCog(Scale): def setup(bot: Snake) -> None: """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" - if get_config().gitlab_token: + if JarvisConfig.from_yaml().gitlab_token: GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index a9af120..f924d7f 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. image processing cog.""" +import logging import re from io import BytesIO @@ -28,6 +29,7 @@ class ImageCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B?)", re.IGNORECASE) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 1b36042..728af87 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -1,10 +1,11 @@ """J.A.R.V.I.S. Remind Me Cog.""" import asyncio +import logging import re -from datetime import datetime, timedelta from typing import List from bson import ObjectId +from dateparser import parse from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel @@ -32,6 +33,10 @@ invites = re.compile( class RemindmeCog(Scale): """J.A.R.V.I.S. Remind Me Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="remindme", description="Set a reminder") @slash_option( name="private", @@ -44,6 +49,14 @@ class RemindmeCog(Scale): ctx: InteractionContext, private: bool = False, ) -> None: + reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))]) + if reminders >= 5: + await ctx.send( + "You already have 5 (or more) active reminders. " + "Please either remove an old one, or wait for one to pass", + ephemeral=True, + ) + return modal = Modal( title="Set your reminder!", components=[ @@ -56,12 +69,13 @@ class RemindmeCog(Scale): ), InputText( label="When to remind you?", - placeholder="1h 30m", + placeholder="1h 30m | in 5 minutes | November 11, 4011", style=TextStyles.SHORT, custom_id="delay", ), ], ) + await ctx.send_modal(modal) try: response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) @@ -82,33 +96,19 @@ class RemindmeCog(Scale): await ctx.send("Hey, you should probably make this readable", ephemeral=True) return - units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"} - delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0} - - if times := time_pattern.findall(delay): - for t in times: - delta[units[t[-1]]] += float(t[:-1]) - else: - await ctx.send( - "Invalid time string, please follow example: `1w 3d 7h 5m 20s`", ephemeral=True + settings = { + "PREFER_DATES_FROM": "future", + "TIMEZONE": "UTC", + "RETURN_AS_TIMEZONE_AWARE": False, + } + remind_at = parse(delay, settings=settings) + if not remind_at: + self.logger.debug(f"Failed to parse delay: {delay}") + await response.send( + f"`{delay}` is not a parsable date, please try again", ephemeral=True ) return - if not any(value for value in delta.items()): - await ctx.send("At least one time period is required", ephemeral=True) - return - - reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))]) - if reminders >= 5: - await ctx.send( - "You already have 5 (or more) active reminders. " - "Please either remove an old one, or wait for one to pass", - ephemeral=True, - ) - return - - remind_at = datetime.utcnow() + timedelta(**delta) - r = Reminder( user=ctx.author.id, channel=ctx.channel.id, diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index ea6b148..7269f3d 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Role Giver Cog.""" import asyncio +import logging from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import get @@ -25,6 +26,7 @@ class RolegiverCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command( name="rolegiver", diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index fec3ee6..29afb7d 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Settings Management Cog.""" +import logging from typing import Any from dis_snek import InteractionContext, Scale, Snake @@ -25,6 +26,10 @@ from jarvis.utils.permissions import admin_or_permissions class SettingsCog(Scale): """J.A.R.V.I.S. Settings Management Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" existing = await Setting.find_one(q(setting=setting, guild=guild)) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 871e25c..b74afb4 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. Starboard Cog.""" +import logging + from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText @@ -32,6 +34,7 @@ class StarboardCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command( name="starboard", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 79521a0..aec012f 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Twitter Cog.""" import asyncio +import logging import tweepy from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -15,7 +16,7 @@ from dis_snek.models.snek.command import check from jarvis_core.db import q from jarvis_core.db.models import TwitterAccount, TwitterFollow -from jarvis import jconfig +from jarvis.config import JarvisConfig from jarvis.utils.permissions import admin_or_permissions @@ -24,7 +25,8 @@ class TwitterCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = jconfig + self.logger = logging.getLogger(__name__) + config = JarvisConfig.from_yaml() auth = tweepy.AppAuthHandler( config.twitter["consumer_key"], config.twitter["consumer_secret"] ) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index aaa8d53..7e195ef 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Utility Cog.""" +import logging import platform import re import secrets @@ -24,7 +25,6 @@ from dis_snek.models.snek.cooldowns import Buckets from PIL import Image import jarvis -from jarvis.config import get_config from jarvis.data import pigpen from jarvis.data.robotcamo import emotes, hk, names from jarvis.utils import build_embed, get_repo_hash @@ -41,7 +41,7 @@ class UtilCog(Scale): def __init__(self, bot: Snake): self.bot = bot - self.config = get_config() + self.logger = logging.getLogger(__name__) @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index a4b0647..f281d97 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Verify Cog.""" import asyncio +import logging from random import randint from dis_snek import InteractionContext, Scale, Snake @@ -34,6 +35,7 @@ class VerifyCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command(name="verify", description="Verify that you've read the rules") @cooldown(bucket=Buckets.USER, rate=1, interval=15) diff --git a/poetry.lock b/poetry.lock index cf974fc..c50d2db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,6 +134,25 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "dateparser" +version = "1.1.1" +description = "Date parsing library designed to parse dates from HTML pages" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +python-dateutil = "*" +pytz = "*" +regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27,<2022.3.15" +tzlocal = "*" + +[package.extras] +calendars = ["convertdate", "hijri-converter", "convertdate"] +fasttext = ["fasttext"] +langdetect = ["langdetect"] + [[package]] name = "dis-snek" version = "7.0.0" @@ -226,7 +245,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.6.1" +version = "0.7.0" description = "" category = "main" optional = false @@ -244,7 +263,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "52a3d568030a79db8ad5ddf65c26216913598bf5" +resolved_reference = "b81ea66d12b1a32c8291cbe9e14fe17b8e020d08" [[package]] name = "jedi" @@ -560,6 +579,17 @@ python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-gitlab" version = "3.2.0" @@ -626,6 +656,25 @@ rope = ["rope (>0.10.5)"] test = ["pylint (>=2.5.0)", "pytest", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5", "flaky"] yapf = ["yapf"] +[[package]] +name = "pytz" +version = "2022.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + [[package]] name = "pyyaml" version = "6.0" @@ -634,6 +683,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "regex" +version = "2022.3.2" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "requests" version = "2.27.1" @@ -689,6 +746,14 @@ python-versions = "*" [package.extras] dev = ["build", "pytest", "pytest-timeout"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "smmap" version = "5.0.0" @@ -748,6 +813,30 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "tzdata" +version = "2022.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + +[[package]] +name = "tzlocal" +version = "4.1" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] + [[package]] name = "ujson" version = "5.1.0" @@ -825,7 +914,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "2adcfd60566d51e43a6f5a3ee0f96140a38e91401800916042cc7cd7e6adb37d" +content-hash = "ce783f7855bb480b335731403679dc8249644570965a4cff6091eb377c5472df" [metadata.files] aiohttp = [ @@ -963,6 +1052,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +dateparser = [ + {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, + {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, +] dis-snek = [ {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, @@ -1502,6 +1595,10 @@ pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] python-gitlab = [ {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, @@ -1514,6 +1611,14 @@ python-lsp-server = [ {file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"}, {file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"}, ] +pytz = [ + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, +] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, +] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1549,6 +1654,82 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] +regex = [ + {file = "regex-2022.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab69b4fe09e296261377d209068d52402fb85ef89dc78a9ac4a29a895f4e24a7"}, + {file = "regex-2022.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5bc5f921be39ccb65fdda741e04b2555917a4bced24b4df14eddc7569be3b493"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43eba5c46208deedec833663201752e865feddc840433285fbadee07b84b464d"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c68d2c04f7701a418ec2e5631b7f3552efc32f6bcc1739369c6eeb1af55f62e0"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:caa2734ada16a44ae57b229d45091f06e30a9a52ace76d7574546ab23008c635"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef806f684f17dbd6263d72a54ad4073af42b42effa3eb42b877e750c24c76f86"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be319f4eb400ee567b722e9ea63d5b2bb31464e3cf1b016502e3ee2de4f86f5c"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:42bb37e2b2d25d958c25903f6125a41aaaa1ed49ca62c103331f24b8a459142f"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:91e0f7e7be77250b808a5f46d90bf0032527d3c032b2131b63dee54753a4d729"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cb3652bbe6720786b9137862205986f3ae54a09dec8499a995ed58292bdf77c2"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:878c626cbca3b649e14e972c14539a01191d79e58934e3f3ef4a9e17f90277f8"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6df070a986fc064d865c381aecf0aaff914178fdf6874da2f2387e82d93cc5bd"}, + {file = "regex-2022.3.2-cp310-cp310-win32.whl", hash = "sha256:b549d851f91a4efb3e65498bd4249b1447ab6035a9972f7fc215eb1f59328834"}, + {file = "regex-2022.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:8babb2b5751105dc0aef2a2e539f4ba391e738c62038d8cb331c710f6b0f3da7"}, + {file = "regex-2022.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1977bb64264815d3ef016625adc9df90e6d0e27e76260280c63eca993e3f455f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e73652057473ad3e6934944af090852a02590c349357b79182c1b681da2c772"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b22ff939a8856a44f4822da38ef4868bd3a9ade22bb6d9062b36957c850e404f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:878f5d649ba1db9f52cc4ef491f7dba2d061cdc48dd444c54260eebc0b1729b9"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0008650041531d0eadecc96a73d37c2dc4821cf51b0766e374cb4f1ddc4e1c14"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06b1df01cf2aef3a9790858af524ae2588762c8a90e784ba00d003f045306204"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57484d39447f94967e83e56db1b1108c68918c44ab519b8ecfc34b790ca52bf7"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74d86e8924835f863c34e646392ef39039405f6ce52956d8af16497af4064a30"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:ae17fc8103f3b63345709d3e9654a274eee1c6072592aec32b026efd401931d0"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5f92a7cdc6a0ae2abd184e8dfd6ef2279989d24c85d2c85d0423206284103ede"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:5dcc4168536c8f68654f014a3db49b6b4a26b226f735708be2054314ed4964f4"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1e30762ddddb22f7f14c4f59c34d3addabc789216d813b0f3e2788d7bcf0cf29"}, + {file = "regex-2022.3.2-cp36-cp36m-win32.whl", hash = "sha256:286ff9ec2709d56ae7517040be0d6c502642517ce9937ab6d89b1e7d0904f863"}, + {file = "regex-2022.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d326ff80ed531bf2507cba93011c30fff2dd51454c85f55df0f59f2030b1687b"}, + {file = "regex-2022.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9d828c5987d543d052b53c579a01a52d96b86f937b1777bbfe11ef2728929357"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87ac58b9baaf50b6c1b81a18d20eda7e2883aa9a4fb4f1ca70f2e443bfcdc57"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6c2441538e4fadd4291c8420853431a229fcbefc1bf521810fbc2629d8ae8c2"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3356afbb301ec34a500b8ba8b47cba0b44ed4641c306e1dd981a08b416170b5"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d96eec8550fd2fd26f8e675f6d8b61b159482ad8ffa26991b894ed5ee19038b"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf668f26604e9f7aee9f8eaae4ca07a948168af90b96be97a4b7fa902a6d2ac1"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb0e2845e81bdea92b8281a3969632686502565abf4a0b9e4ab1471c863d8f3"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:87bc01226cd288f0bd9a4f9f07bf6827134dc97a96c22e2d28628e824c8de231"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:09b4b6ccc61d4119342b26246ddd5a04accdeebe36bdfe865ad87a0784efd77f"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:9557545c10d52c845f270b665b52a6a972884725aa5cf12777374e18f2ea8960"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:0be0c34a39e5d04a62fd5342f0886d0e57592a4f4993b3f9d257c1f688b19737"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7b103dffb9f6a47ed7ffdf352b78cfe058b1777617371226c1894e1be443afec"}, + {file = "regex-2022.3.2-cp37-cp37m-win32.whl", hash = "sha256:f8169ec628880bdbca67082a9196e2106060a4a5cbd486ac51881a4df805a36f"}, + {file = "regex-2022.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9c16a807b17b17c4fa3a1d8c242467237be67ba92ad24ff51425329e7ae3d0"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67250b36edfa714ba62dc62d3f238e86db1065fccb538278804790f578253640"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5510932596a0f33399b7fff1bd61c59c977f2b8ee987b36539ba97eb3513584a"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f7ee2289176cb1d2c59a24f50900f8b9580259fa9f1a739432242e7d254f93"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d7a68fa53688e1f612c3246044157117403c7ce19ebab7d02daf45bd63913e"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf5317c961d93c1a200b9370fb1c6b6836cc7144fef3e5a951326912bf1f5a3"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad397bc7d51d69cb07ef89e44243f971a04ce1dca9bf24c992c362406c0c6573"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:297c42ede2c81f0cb6f34ea60b5cf6dc965d97fa6936c11fc3286019231f0d66"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af4d8cc28e4c7a2f6a9fed544228c567340f8258b6d7ea815b62a72817bbd178"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:452519bc4c973e961b1620c815ea6dd8944a12d68e71002be5a7aff0a8361571"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cb34c2d66355fb70ae47b5595aafd7218e59bb9c00ad8cc3abd1406ca5874f07"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d146e5591cb67c5e836229a04723a30af795ef9b70a0bbd913572e14b7b940f"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:03299b0bcaa7824eb7c0ebd7ef1e3663302d1b533653bfe9dc7e595d453e2ae9"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ccb0a4ab926016867260c24c192d9df9586e834f5db83dfa2c8fffb3a6e5056"}, + {file = "regex-2022.3.2-cp38-cp38-win32.whl", hash = "sha256:f7e8f1ee28e0a05831c92dc1c0c1c94af5289963b7cf09eca5b5e3ce4f8c91b0"}, + {file = "regex-2022.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35ed2f3c918a00b109157428abfc4e8d1ffabc37c8f9abc5939ebd1e95dabc47"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:55820bc631684172b9b56a991d217ec7c2e580d956591dc2144985113980f5a3"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:83f03f0bd88c12e63ca2d024adeee75234d69808b341e88343b0232329e1f1a1"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42d6007722d46bd2c95cce700181570b56edc0dcbadbfe7855ec26c3f2d7e008"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:320c2f4106962ecea0f33d8d31b985d3c185757c49c1fb735501515f963715ed"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e51ad1e6131c496b58d317bc9abec71f44eb1957d32629d06013a21bc99cac"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bc3a5effa5974be6d965ed8301ac1e869bc18425c8a8fac179fbe7876e3aee"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e5602a9b5074dcacc113bba4d2f011d2748f50e3201c8139ac5b68cf2a76bd8b"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:729aa8ca624c42f309397c5fc9e21db90bf7e2fdd872461aabdbada33de9063c"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d6ecfd1970b3380a569d7b3ecc5dd70dba295897418ed9e31ec3c16a5ab099a5"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:13bbf0c9453c6d16e5867bda7f6c0c7cff1decf96c5498318bb87f8136d2abd4"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:58ba41e462653eaf68fc4a84ec4d350b26a98d030be1ab24aba1adcc78ffe447"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c0446b2871335d5a5e9fcf1462f954586b09a845832263db95059dcd01442015"}, + {file = "regex-2022.3.2-cp39-cp39-win32.whl", hash = "sha256:20e6a27959f162f979165e496add0d7d56d7038237092d1aba20b46de79158f1"}, + {file = "regex-2022.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9efa41d1527b366c88f265a227b20bcec65bda879962e3fc8a2aee11e81266d7"}, + {file = "regex-2022.3.2.tar.gz", hash = "sha256:79e5af1ff258bc0fe0bdd6f69bc4ae33935a898e3cbefbbccf22e88a27fa053b"}, +] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, @@ -1565,6 +1746,10 @@ rope = [ {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, ] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -1589,6 +1774,14 @@ typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] +tzdata = [ + {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, + {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, +] +tzlocal = [ + {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, + {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, +] ujson = [ {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, {file = "ujson-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0cae4a9c141856f7ad1a79c17ff1aaebf7fd8faa2f2c2614c37d6f82ed261d96"}, diff --git a/pyproject.toml b/pyproject.toml index d63854d..110df1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ orjson = "^3.6.6" jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} aiohttp = "^3.8.1" pastypy = "^1.0.1" +dateparser = "^1.1.1" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 1225034f72a948636ed11ea3612fa886d82e743e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 19:04:31 -0600 Subject: [PATCH 14/95] More logging --- jarvis/client.py | 7 +++++-- jarvis/cogs/admin/warning.py | 3 +++ jarvis/cogs/dev.py | 3 ++- jarvis/cogs/remindme.py | 23 +++++++++++++++++++++++ jarvis/cogs/verify.py | 2 ++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 74e6020..9f99005 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -407,7 +407,7 @@ class Jarvis(Snake): url=after.jump_url, ) embed.set_footer( - text=f"{after.author.user.username}#{after.author.discriminator} | {after.author.id}" + text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" ) await channel.send(embed=embed) if not isinstance(after.channel, DMChannel) and not after.author.bot: @@ -475,6 +475,9 @@ class Jarvis(Snake): url=message.jump_url, ) embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + text=( + f"{message.author.username}#{message.author.discriminator} | " + f"{message.author.id}" + ) ) await channel.send(embed=embed) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 3ef1a8a..0c15964 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -87,6 +87,9 @@ class WarningCog(ModcaseCog): .sort("created_at", -1) .to_list(None) ) + if len(warnings) == 0: + await ctx.send("That user has no warnings.", ephemeral=True) + return active_warns = get_all(warnings, active=True) pages = [] diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 5d419e3..946e374 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -88,8 +88,9 @@ class DevCog(Scale): title = attach.filename elif url.match(data): try: - if await get_size(data) > MAX_FILESIZE: + if (size := await get_size(data)) > MAX_FILESIZE: await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + self.logger.debug(f"Refused to hash file of size {convert_bytesize(size)}") return except Exception as e: await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 728af87..c5bcd21 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -277,6 +277,29 @@ class RemindmeCog(Scale): component.disabled = True await message.edit(components=components) + @slash_command( + name="reminders", + sub_cmd_name="fetch", + sub_cmd_description="Fetch a reminder that failed to send", + ) + @slash_option( + name="id", description="ID of the reminder", opt_type=OptionTypes.STRING, required=True + ) + async def _fetch(self, ctx: InteractionContext, id: str) -> None: + reminder = await Reminder.find_one(q(id=id)) + if not reminder: + await ctx.send(f"Reminder {id} does not exist") + return + + embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) + embed.set_author( + name=ctx.author.display_name + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar, + ) + + embed.set_thumbnail(url=ctx.author.display_avatar) + await ctx.send(embed=embed, ephemeral=reminder.private) + def setup(bot: Snake) -> None: """Add RemindmeCog to J.A.R.V.I.S.""" diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index f281d97..05a723d 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -81,6 +81,7 @@ class VerifyCog(Scale): components=components, ) await response.context.message.delete(delay=5) + self.logger.debug(f"User {ctx.author.id} verified successfully") else: await response.context.edit_origin( content=( @@ -90,6 +91,7 @@ class VerifyCog(Scale): ) except asyncio.TimeoutError: await message.delete(delay=30) + self.logger.debug(f"User {ctx.author.id} failed to verify before timeout") def setup(bot: Snake) -> None: From 3b12ffa7f0d4153479156683e48a2d88a800ce52 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 20:30:57 -0600 Subject: [PATCH 15/95] Fix errors not closing modal --- jarvis/cogs/remindme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index c5bcd21..9c14804 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -84,16 +84,16 @@ class RemindmeCog(Scale): except asyncio.TimeoutError: return if len(message) > 500: - await ctx.send("Reminder cannot be > 500 characters.", ephemeral=True) + await response.send("Reminder cannot be > 500 characters.", ephemeral=True) return elif invites.search(message): - await ctx.send( + await response.send( "Listen, don't use this to try and bypass the rules", ephemeral=True, ) return elif not valid.fullmatch(message): - await ctx.send("Hey, you should probably make this readable", ephemeral=True) + await response.send("Hey, you should probably make this readable", ephemeral=True) return settings = { From dffd5f780ff5554834518c05402880cdf6e1d834 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:45:41 -0600 Subject: [PATCH 16/95] Cooldown handling, formatting --- .flake8 | 1 + jarvis/client.py | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index 50c8d97..36c40f6 100644 --- a/.flake8 +++ b/.flake8 @@ -11,5 +11,6 @@ extend-ignore = D101, # Missing docstring in public class # Plugins we don't currently include: flake8-return + R502, # do not implicitly return None in function able to return non-None value. R503, # missing explicit return at the end of function ableto return non-None value. max-line-length=100 diff --git a/jarvis/client.py b/jarvis/client.py index 9f99005..4619f84 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -7,6 +7,7 @@ from datetime import datetime from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField @@ -95,14 +96,23 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" + if isinstance(error, CommandOnCooldown): + await ctx.send(str(error), ephemeral=True) + return + elif isinstance(error, CommandCheckFailure): + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) + return guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") timestamp = int(datetime.now().timestamp()) timestamp = f"" - arg_str = ( - "\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None" - ) + arg_str = "" + for k, v in ctx.kwargs.items(): + arg_str += f" {k}: " + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f"{v}\n" callback_args = "\n".join(f" - {i}" for i in args) if args else " None" callback_kwargs = ( "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" @@ -141,7 +151,14 @@ class Jarvis(Snake): modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) - args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items()) + args = [] + for k, v in ctx.kwargs.items(): + if isinstance(v, str): + v = v.replace("`", "\\`") + if len(v) > 100: + v = v[:97] + "..." + args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + args = " ".join(args) fields = [ EmbedField( name="Command", From 58e0ee892b2e803075efc382171d34adc6227c5d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:46:47 -0600 Subject: [PATCH 17/95] Formatting changes, delay changes --- jarvis/cogs/admin/ban.py | 25 +++++++++++++------------ jarvis/cogs/verify.py | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 6ca8447..0f18c7d 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -62,9 +62,9 @@ class BanCog(ModcaseCog): embed.set_author( name=user.display_name, - icon_url=user.avatar, + icon_url=user.avatar.url, ) - embed.set_thumbnail(url=user.avatar) + embed.set_thumbnail(url=user.avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) @@ -89,9 +89,9 @@ class BanCog(ModcaseCog): ) embed.set_author( name=user.username, - icon_url=user.avatar, + icon_url=user.avatar.url, ) - embed.set_thumbnail(url=user.avatar) + embed.set_thumbnail(url=user.avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) @@ -121,15 +121,15 @@ class BanCog(ModcaseCog): async def _ban( self, ctx: InteractionContext, + user: User, reason: str, - user: User = None, btype: str = "perm", duration: int = 4, ) -> None: - if not user or user == ctx.author: + if user.id == ctx.author.id: await ctx.send("You cannot ban yourself.", ephemeral=True) return - if user == self.bot.user: + if user.id == self.bot.user.id: await ctx.send("I'm afraid I can't let you do that", ephemeral=True) return if btype == "temp" and duration < 0: @@ -215,9 +215,10 @@ class BanCog(ModcaseCog): discord_ban_info = None database_ban_info = None - bans = await ctx.guild.bans() + bans = await ctx.guild.fetch_bans() # Try to get ban information out of Discord + self.logger.debug(f"{user}") if re.match(r"^[0-9]{1,}$", user): # User ID user = int(user) discord_ban_info = find(lambda x: x.user.id == user, bans) @@ -252,9 +253,9 @@ class BanCog(ModcaseCog): # try to find the relevant information in the database. # We take advantage of the previous checks to save CPU cycles if not discord_ban_info: - if isinstance(user, int): + if isinstance(user, User): database_ban_info = await Ban.find_one( - q(guild=ctx.guild.id, user=user, active=True) + q(guild=ctx.guild.id, user=user.id, active=True) ) else: search = { @@ -320,10 +321,10 @@ class BanCog(ModcaseCog): search["active"] = True if btype > 0: search["type"] = types[btype] - bans = Ban.find(search).sort([("created_at", -1)]) + bans = await Ban.find(search).sort([("created_at", -1)]).to_list(None) db_bans = [] fields = [] - async for ban in bans: + for ban in bans: if not ban.username: user = await self.bot.fetch_user(ban.user) ban.username = user.username if user else "[deleted user]" diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 05a723d..f3cbe29 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -38,7 +38,7 @@ class VerifyCog(Scale): self.logger = logging.getLogger(__name__) @slash_command(name="verify", description="Verify that you've read the rules") - @cooldown(bucket=Buckets.USER, rate=1, interval=15) + @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _verify(self, ctx: InteractionContext) -> None: await ctx.defer() role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) @@ -90,7 +90,7 @@ class VerifyCog(Scale): ) ) except asyncio.TimeoutError: - await message.delete(delay=30) + await message.delete(delay=2) self.logger.debug(f"User {ctx.author.id} failed to verify before timeout") From a6252e262c0a56342890e47c1400030c2f983115 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:47:24 -0600 Subject: [PATCH 18/95] Catch failed DM to user --- jarvis/cogs/admin/kick.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index e00f096..4175987 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -59,7 +59,11 @@ class KickCog(ModcaseCog): await user.send(embed=embed) except Exception: send_failed = True - await ctx.guild.kick(user, reason=reason) + try: + await ctx.guild.kick(user, reason=reason) + except Exception as e: + await ctx.send(f"Failed to kick user:\n```\n{e}\n```", ephemeral=True) + return fields = [EmbedField(name="DM Sent?", value=str(not send_failed))] embed = build_embed( From f1f19366738ccba582d6490a61f2a0cde965e37e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:47:35 -0600 Subject: [PATCH 19/95] Merge all perms into one for simplicity sake --- jarvis/cogs/admin/lock.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index e1aeb61..7c8f4a2 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -67,11 +67,7 @@ class LockCog(Scale): if not channel: channel = ctx.channel - # role = ctx.guild.default_role # Uncomment once implemented - if isinstance(channel, GuildText): - to_deny = Permissions.SEND_MESSAGES - elif isinstance(channel, GuildVoice): - to_deny = Permissions.CONNECT | Permissions.SPEAK + to_deny = Permissions.CONNECT | Permissions.SPEAK | Permissions.SEND_MESSAGES current = get(channel.permission_overwrites, id=ctx.guild.id) if current: From 9f65213ea6cffaf72b01635cb30b37db1b34de0d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:03 -0600 Subject: [PATCH 20/95] URL fixes, catches for empty/non-existent data --- jarvis/cogs/util.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 7e195ef..61b3da2 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -13,7 +13,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.role import Role -from dis_snek.models.discord.user import User +from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -54,6 +54,12 @@ class UtilCog(Scale): fields.append(EmbedField(name="dis-snek", value=const.__version__)) fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) + num_domains = len(self.bot.phishing_domains) + fields.append( + EmbedField( + name="Phishing Protection", value=f"Detecting {num_domains} phishing domains" + ) + ) embed = build_embed(title=title, description=desc, fields=fields, color=color) await ctx.send(embed=embed) @@ -96,6 +102,8 @@ class UtilCog(Scale): to_send += f":{names[id]}:" if len(to_send) > 2000: await ctx.send("Too long.", ephemeral=True) + elif len(to_send) == 0: + await ctx.send("No valid text found", ephemeral=True) else: await ctx.send(to_send) @@ -111,7 +119,10 @@ class UtilCog(Scale): if not user: user = ctx.author - avatar = user.display_avatar.url + avatar = user.avatar.url + if isinstance(user, Member): + avatar = user.display_avatar.url + embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE") embed.set_image(url=avatar) embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar) @@ -175,8 +186,12 @@ class UtilCog(Scale): required=False, ) async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: + await ctx.defer() if not user: user = ctx.author + if not await ctx.guild.fetch_member(user.id): + await ctx.send("That user isn't in this guild.", ephemeral=True) + return user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) From 0729bce29564a501585c3119488fca3d78716bcd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:20 -0600 Subject: [PATCH 21/95] Better date parsing to be future-only --- jarvis/cogs/remindme.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9c14804..05e6d81 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,10 +2,12 @@ import asyncio import logging import re +from datetime import datetime from typing import List from bson import ObjectId from dateparser import parse +from dateparser_data.settings import default_parsers from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel @@ -96,12 +98,27 @@ class RemindmeCog(Scale): await response.send("Hey, you should probably make this readable", ephemeral=True) return - settings = { + base_settings = { "PREFER_DATES_FROM": "future", "TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": False, } - remind_at = parse(delay, settings=settings) + rt_settings = base_settings.copy() + rt_settings["PARSERS"] = [ + x for x in default_parsers if x not in ["absolute-time", "timestamp"] + ] + + rt_remind_at = parse(delay, settings=rt_settings) + + at_settings = base_settings.copy() + at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] + at_remind_at = parse(delay, settings=at_settings) + + if rt_remind_at and at_remind_at: + remind_at = max(rt_remind_at, at_remind_at) + else: + remind_at = rt_remind_at or at_remind_at + if not remind_at: self.logger.debug(f"Failed to parse delay: {delay}") await response.send( @@ -109,6 +126,15 @@ class RemindmeCog(Scale): ) return + if remind_at < datetime.utcnow(): + await response.send( + f"`{delay}` is in the past. Past reminders aren't allowed", ephemeral=True + ) + return + + elif remind_at < datetime.utcnow(): + pass + r = Reminder( user=ctx.author.id, channel=ctx.channel.id, From 02d50180f12cf80fd9126ce7fd96a10124457679 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:33 -0600 Subject: [PATCH 22/95] Verify that target sizes are sane --- jarvis/cogs/image.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index f924d7f..d15fdd8 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -58,6 +58,7 @@ class ImageCog(Scale): async def _resize( self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None ) -> None: + await ctx.defer() if not attachment and not url: await ctx.send("A URL or attachment is required", ephemeral=True) return @@ -73,10 +74,18 @@ class ImageCog(Scale): ) return - tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) + try: + tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) + except ValueError: + await ctx.send("Failed to read your target size. Try a more sane one", ephemeral=True) + return + if tgt_size > unconvert_bytesize(8, "MB"): await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True) return + elif tgt_size < 1024: + await ctx.send("Sizes < 1KB are extremely unreliable and are disabled", ephemeral=True) + return if attachment: url = attachment.url @@ -91,7 +100,7 @@ class ImageCog(Scale): size = len(data) if size <= tgt_size: - await ctx.send("Image already meets target.") + await ctx.send("Image already meets target.", ephemeral=True) return ratio = max(tgt_size / size - 0.02, 0.50) From 38b86d46943b71c1a10aa6ec413c0791a0b02f41 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:44 -0600 Subject: [PATCH 23/95] Catch encoding errors --- jarvis/cogs/dev.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 946e374..f746d23 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -207,7 +207,11 @@ class DevCog(Scale): async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "encode") - encoded = method(data.encode("UTF-8")).decode("UTF-8") + try: + encoded = method(data.encode("UTF-8")).decode("UTF-8") + except Exception as e: + await ctx.send(f"Failed to encode data: {e}") + return fields = [ EmbedField(name="Plaintext", value=f"`{data}`", inline=False), EmbedField(name=mstr, value=f"`{encoded}`", inline=False), @@ -232,7 +236,11 @@ class DevCog(Scale): async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "decode") - decoded = method(data.encode("UTF-8")).decode("UTF-8") + try: + decoded = method(data.encode("UTF-8")).decode("UTF-8") + except Exception as e: + await ctx.send(f"Failed to decode data: {e}") + return if invites.search(decoded): await ctx.send( "Please don't use this to bypass invite restrictions", From 0c97e8096a6bc6d85108e3d36f7aeefac063f959 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:51 -0600 Subject: [PATCH 24/95] Delete invalid rolepings --- jarvis/cogs/admin/roleping.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 9ef8152..47c0291 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -75,6 +75,9 @@ class RolepingCog(Scale): embeds = [] for roleping in rolepings: role = await ctx.guild.fetch_role(roleping.role) + if not role: + await roleping.delete() + continue broles = find_all(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles) bypass_roles = [r.mention or "||`[redacted]`||" for r in broles] bypass_users = [ From 5d3af23f0ef48f7685eeb62785e2cd1d2d686cc1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:27:26 -0600 Subject: [PATCH 25/95] Fix timestamps being wrong --- jarvis/client.py | 11 ++++++----- jarvis/cogs/admin/mute.py | 14 ++++++++++---- jarvis/cogs/remindme.py | 6 +++--- jarvis/utils/cogs.py | 4 ++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 4619f84..04d44bb 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -2,7 +2,7 @@ import logging import re import traceback -from datetime import datetime +from datetime import datetime, timezone from aiohttp import ClientSession from dis_snek import Snake, listen @@ -104,7 +104,7 @@ class Jarvis(Snake): return guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) - error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") + error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC") timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" @@ -124,18 +124,19 @@ class Jarvis(Snake): callback_args=callback_args, callback_kwargs=callback_kwargs, ) - if len(full_message) >= 1900: - error_message = " ".join(traceback.format_exception(error)) + error_message = "".join(traceback.format_exception(error)) + if len(full_message + error_message) >= 1800: + error_message = " ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message paste = Paste(content=full_message) await paste.save(DEFAULT_SITE) + self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." f"\nPlease see log at {paste.url}" ) else: - error_message = "".join(traceback.format_exception(error)) await channel.send( f"JARVIS encountered an error at {timestamp}:" f"\n```yaml\n{full_message}\n```" diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index a8c7666..4ccf7f1 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,6 +1,6 @@ """J.A.R.V.I.S. MuteCog.""" import logging -from datetime import datetime +from datetime import datetime, timedelta, timezone from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField @@ -76,7 +76,8 @@ class MuteCog(ModcaseCog): await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) return - await user.timeout(communication_disabled_until=duration, reason=reason) + until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) + await user.timeout(communication_disabled_until=until, reason=reason) m = Mute( user=user.id, reason=reason, @@ -86,11 +87,15 @@ class MuteCog(ModcaseCog): active=True, ) await m.commit() + ts = int(until.timestamp()) embed = build_embed( title="User Muted", description=f"{user.mention} has been muted", - fields=[EmbedField(name="Reason", value=reason)], + fields=[ + EmbedField(name="Reason", value=reason), + EmbedField(name="Until", value=f" "), + ], ) embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) embed.set_thumbnail(url=user.display_avatar.url) @@ -109,7 +114,8 @@ class MuteCog(ModcaseCog): async def _unmute(self, ctx: InteractionContext, user: Member) -> None: if ( not user.communication_disabled_until - or user.communication_disabled_until < datetime.now() # noqa: W503 + or user.communication_disabled_until.timestamp() + < datetime.now(tz=timezone.utc).timestamp() # noqa: W503 ): await ctx.send("User is not muted", ephemeral=True) return diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 05e6d81..b03271b 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,7 +2,7 @@ import asyncio import logging import re -from datetime import datetime +from datetime import datetime, timezone from typing import List from bson import ObjectId @@ -126,13 +126,13 @@ class RemindmeCog(Scale): ) return - if remind_at < datetime.utcnow(): + if remind_at < datetime.now(tz=timezone.utc): await response.send( f"`{delay}` is in the past. Past reminders aren't allowed", ephemeral=True ) return - elif remind_at < datetime.utcnow(): + elif remind_at < datetime.now(tz=timezone.utc): pass r = Reminder( diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index f9361c3..a235c3c 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,5 +1,5 @@ """Cog wrapper for command caching.""" -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from dis_snek import Context, Scale, Snake from dis_snek.client.utils.misc_utils import find @@ -35,7 +35,7 @@ class CacheCog(Scale): async def _expire_interaction(self) -> None: keys = list(self.cache.keys()) for key in keys: - if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): + if self.cache[key]["timeout"] <= datetime.now(tz=timezone.utc) + timedelta(minutes=1): del self.cache[key] From f8e1d88c5e5d7a21cadd7ecd3fb281321a16e8ae Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:32:16 -0600 Subject: [PATCH 26/95] Fix warnings.count -> len(warnings) --- jarvis/cogs/admin/warning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 0c15964..d70edf1 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -97,7 +97,7 @@ class WarningCog(ModcaseCog): if len(active_warns) == 0: embed = build_embed( title="Warnings", - description=f"{warnings.count()} total | 0 currently active", + description=f"{len(warnings)} total | 0 currently active", fields=[], ) embed.set_author(name=user.username, icon_url=user.display_avatar.url) From 7084c7fd055b2483fdbea68b6872d19c790a66eb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:36:35 -0600 Subject: [PATCH 27/95] Update warnings embed display --- jarvis/cogs/admin/warning.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index d70edf1..e8b9dbb 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -107,12 +107,13 @@ class WarningCog(ModcaseCog): fields = [] for warn in active_warns: admin = await ctx.guild.fetch_member(warn.admin) + ts = int(warn.created_at.timestamp()) admin_name = "||`[redacted]`||" if admin: admin_name = f"{admin.username}#{admin.discriminator}" fields.append( EmbedField( - name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"), + name=f"", value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b", inline=False, ) @@ -135,8 +136,9 @@ class WarningCog(ModcaseCog): else: fields = [] for warn in warnings: + ts = int(warn.created_at.timestamp()) title = "[A] " if warn.active else "[I] " - title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC") + title += f"" fields.append( EmbedField( name=title, From e91b774ffff8b557fcb79046e5ec5297e19a2cdc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:00:07 -0600 Subject: [PATCH 28/95] Use new slash command syntax --- jarvis/cogs/admin/ban.py | 7 +- jarvis/cogs/admin/lockdown.py | 9 +- jarvis/cogs/admin/roleping.py | 44 +++--- jarvis/cogs/autoreact.py | 13 +- jarvis/cogs/ctc2.py | 16 +- jarvis/cogs/dbrand.py | 134 ++++------------ jarvis/cogs/gl.py | 28 ++-- jarvis/cogs/remindme.py | 10 +- jarvis/cogs/rolegiver.py | 24 +-- jarvis/cogs/settings.py | 290 ++++++++++++++++------------------ jarvis/cogs/starboard.py | 25 ++- jarvis/cogs/twitter.py | 14 +- 12 files changed, 252 insertions(+), 362 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 0f18c7d..242c624 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -9,6 +9,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, SlashCommandChoice, slash_command, slash_option, @@ -290,9 +291,9 @@ class BanCog(ModcaseCog): ).save() await ctx.send("Unable to find user in Discord, but removed entry from database.") - @slash_command( - name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans" - ) + bans = SlashCommand(name="bans", description="User bans") + + @bans.subcommand(sub_cmd_name="list", sub_cmd_description="List bans") @slash_option( name="btype", description="Ban type", diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index e55b39e..527bfa5 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -99,9 +99,12 @@ class LockdownCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( + lockdown = SlashCommand( name="lockdown", description="Manage server-wide lockdown", + ) + + @lockdown.subcommand( sub_cmd_name="start", sub_cmd_description="Lockdown the server", ) @@ -148,7 +151,7 @@ class LockdownCog(Scale): ).commit() await ctx.send("Server now in lockdown.") - @slash_command(name="lockdown", sub_cmd_name="end", sub_cmd_description="End a lockdown") + @lockdown.subcommand(sub_cmd_name="end", sub_cmd_description="End a lockdown") @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_end( self, diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 47c0291..e1cb6d7 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.role import Role from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -27,9 +27,11 @@ class RolepingCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="roleping", - description="Set up warnings for pinging specific roles", + roleping = SlashCommand( + name="roleping", description="Set up warnings for pinging specific roles" + ) + + @roleping.subcommand( sub_cmd_name="add", sub_cmd_description="Add a role to roleping", ) @@ -50,7 +52,7 @@ class RolepingCog(Scale): ).commit() await ctx.send(f"Role `{role.name}` added to roleping.") - @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @roleping.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role") @slash_option( name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) @@ -64,7 +66,7 @@ class RolepingCog(Scale): await roleping.delete() await ctx.send(f"Role `{role.name}` removed from roleping.") - @slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles") + @roleping.subcommand(sub_cmd_name="list", sub_cmd_description="Lick all blocklisted roles") async def _roleping_list(self, ctx: InteractionContext) -> None: rolepings = await Roleping.find(q(guild=ctx.guild.id)).to_list(None) @@ -123,11 +125,11 @@ class RolepingCog(Scale): await paginator.send(ctx) - @slash_command( - name="roleping", - description="Block roles from being pinged", - group_name="bypass", - group_description="Allow specific users/roles to ping rolepings", + bypass = roleping.group( + name="bypass", description="Allow specific users/roles to ping rolepings" + ) + + @bypass.subcommand( sub_cmd_name="user", sub_cmd_description="Add a user as a bypass to a roleping", ) @@ -170,11 +172,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.display_name} user bypass added for `{role.name}`") - @slash_command( - name="roleping", - group_name="bypass", + @bypass.subcommand( sub_cmd_name="role", - description="Add a role as a bypass to roleping", + sub_cmd_description="Add a role as a bypass to roleping", ) @slash_option( name="bypass", description="Role to add", opt_type=OptionTypes.ROLE, required=True @@ -207,11 +207,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.name} role bypass added for `{role.name}`") - @slash_command( - name="roleping", - description="Block roles from being pinged", - group_name="restore", - group_description="Remove a roleping bypass", + restore = roleping.group(name="restore", description="Remove a roleping bypass") + + @restore.subcommand( sub_cmd_name="user", sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @@ -238,11 +236,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") - @slash_command( - name="roleping", - group_name="restore", + @restore.subcommand( sub_cmd_name="role", - description="Remove a bypass from a roleping (restoring it)", + sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @slash_option( name="bypass", description="Role to remove", opt_type=OptionTypes.ROLE, required=True diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 40a5012..9d421f2 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -8,7 +8,7 @@ from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -72,8 +72,9 @@ class AutoReactCog(Scale): return True return False - @slash_command( - name="autoreact", + autoreact = SlashCommand(name="autoreact", description="Channel message autoreacts") + + @autoreact.subcommand( sub_cmd_name="add", sub_cmd_description="Add an autoreact emote to a channel", ) @@ -134,8 +135,7 @@ class AutoReactCog(Scale): message += f" Set autoreact thread creation to {thread} in {channel.mention}" await ctx.send(message) - @slash_command( - name="autoreact", + @autoreact.subcommand( sub_cmd_name="remove", sub_cmd_description="Remove an autoreact emote to a channel", ) @@ -178,8 +178,7 @@ class AutoReactCog(Scale): await self.delete_autoreact(ctx, channel) await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") - @slash_command( - name="autoreact", + @autoreact.subcommand( sub_cmd_name="list", sub_cmd_description="List all autoreacts on a channel", ) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 8d4e71b..31658a9 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -7,7 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User -from dis_snek.models.snek.application_commands import slash_command +from dis_snek.models.snek.application_commands import SlashCommand from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -36,18 +36,16 @@ class CTCCog(Scale): def __del__(self): self._session.close() - @slash_command( - name="ctc2", sub_cmd_name="about", description="CTC2 related commands", scopes=guild_ids - ) + ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopres=guild_ids) + + @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _about(self, ctx: InteractionContext) -> None: await ctx.send("See https://completethecode.com for more information") - @slash_command( - name="ctc2", + @ctc2.subcommand( sub_cmd_name="pw", sub_cmd_description="Guess a password for https://completethecodetwo.cards", - scopes=guild_ids, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _pw(self, ctx: InteractionContext, guess: str) -> None: @@ -89,11 +87,9 @@ class CTCCog(Scale): await ctx.send("Nope.", ephemeral=True) _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() - @slash_command( - name="ctc2", + @ctc2.subcommand( sub_cmd_name="guesses", sub_cmd_description="Show guesses made for https://completethecodetwo.cards", - scopes=guild_ids, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 761ff12..5cea582 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -7,7 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import cooldown @@ -17,7 +17,7 @@ from jarvis.config import get_config from jarvis.data.dbrand import shipping_lookup from jarvis.utils import build_embed -guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] +guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862402786116763668] class DbrandCog(Scale): @@ -39,121 +39,53 @@ class DbrandCog(Scale): def __del__(self): self._session.close() - @slash_command( - name="db", - sub_cmd_name="skin", - scopes=guild_ids, - sub_cmd_description="See what skins are available", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _skin(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "/skins") + db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids) - @slash_command( - name="db", - sub_cmd_name="robotcamo", - scopes=guild_ids, - sub_cmd_description="Get some robot camo. Make Tony Stark proud", - ) + @db.subcommand(sub_cmd_name="info", sub_cmd_description="Get useful links") @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _camo(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "robot-camo") + async def _info(self, ctx: InteractionContext) -> None: + urls = [ + f"[Get Skins]({self.base_url + 'skins'})", + f"[Robot Camo]({self.base_url + 'robot-camo'})", + f"[Get a Grip]({self.base_url + 'grip'})", + f"[Shop All Products]({self.base_url + 'shop'})", + f"[Order Status]({self.base_url + 'order-status'})", + f"[dbrand Status]({self.base_url + 'status'})", + f"[Be (not) extorted]({self.base_url + 'not-extortion'})", + "[Robot Camo Wallpapers](https://db.io/wallpapers)", + ] + embed = build_embed( + title="Useful Links", description="\n\n".join(urls), fields=[], color="#FFBB00" + ) + embed.set_footer( + text="dbrand.com", + icon_url="https://dev.zevaryx.com/db_logo.png", + ) + embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png") + embed.set_author( + name="dbrand", url=self.base_url, icon_url="https://dev.zevaryx.com/db_logo.png" + ) + await ctx.send(embed=embed) - @slash_command( - name="db", - sub_cmd_name="grip", - scopes=guild_ids, - sub_cmd_description="See devices with Grip support", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _grip(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "grip") - - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="contact", - scopes=guild_ids, sub_cmd_description="Contact support", ) @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _contact(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="support", - scopes=guild_ids, sub_cmd_description="Contact support", ) @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _support(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @slash_command( - name="db", - sub_cmd_name="orderstat", - scopes=guild_ids, - sub_cmd_description="Get your order status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _orderstat(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "order-status") - - @slash_command( - name="db", - sub_cmd_name="orders", - scopes=guild_ids, - sub_cmd_description="Get your order status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _orders(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "order-status") - - @slash_command( - name="db", - sub_cmd_name="status", - scopes=guild_ids, - sub_cmd_description="dbrand status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _status(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "status") - - @slash_command( - name="db", - sub_cmd_name="buy", - scopes=guild_ids, - sub_cmd_description="Give us your money!", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _buy(self, ctx: InteractionContext) -> None: - await ctx.send("Give us your money! " + self.base_url + "shop") - - @slash_command( - name="db", - sub_cmd_name="extortion", - scopes=guild_ids, - sub_cmd_description="(not) extortion", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _extort(self, ctx: InteractionContext) -> None: - await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion") - - @slash_command( - name="db", - sub_cmd_name="wallpapers", - sub_cmd_description="Robot Camo Wallpapers", - scopes=guild_ids, - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _wallpapers(self, ctx: InteractionContext) -> None: - await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers") - - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="ship", sub_cmd_description="Get shipping information for your country", - scopes=guild_ids, ) @slash_option( name="search", @@ -217,7 +149,7 @@ class DbrandCog(Scale): ) embed = build_embed( title="Shipping to {}".format(data["country"]), - sub_cmd_description=description, + description=description, color="#FFBB00", fields=fields, url=self.base_url + "shipping/" + country, @@ -231,7 +163,7 @@ class DbrandCog(Scale): elif not data["is_valid"]: embed = build_embed( title="Check Shipping Times", - sub_cmd_description=( + description=( "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)" ), @@ -248,7 +180,7 @@ class DbrandCog(Scale): elif not data["shipping_available"]: embed = build_embed( title="Shipping to {}".format(data["country"]), - sub_cmd_description=( + description=( "No shipping available.\nTime to move to a country" " that has shipping available.\nYou can [find a new country " "to live in here](https://dbrand.com/shipping)" diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 6bb92d0..80c6a31 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -11,6 +11,7 @@ from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, SlashCommandChoice, slash_command, slash_option, @@ -35,12 +36,11 @@ class GitlabCog(Scale): # J.A.R.V.I.S. GitLab ID is 29 self.project = self._gitlab.projects.get(29) - @slash_command( - name="gl", - description="Get GitLab info", + gl = SlashCommand(name="gl", description="Get GitLab info", scopes=guild_ids) + + @gl.subcommand( sub_cmd_name="issue", sub_cmd_description="Get an issue from GitLab", - scopes=guild_ids, ) @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) async def _issue(self, ctx: InteractionContext, id: int) -> None: @@ -104,11 +104,9 @@ class GitlabCog(Scale): ) await ctx.send(embed=embed) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="milestone", sub_cmd_description="Get a milestone from GitLab", - scopes=guild_ids, ) @slash_option( name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True @@ -160,11 +158,9 @@ class GitlabCog(Scale): ) await ctx.send(embed=embed) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="mr", sub_cmd_description="Get a merge request from GitLab", - scopes=guild_ids, ) @slash_option( name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True @@ -272,11 +268,9 @@ class GitlabCog(Scale): ) return embed - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="issues", sub_cmd_description="Get issues from GitLab", - scopes=guild_ids, ) @slash_option( name="state", @@ -328,11 +322,9 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="mrs", sub_cmd_description="Get merge requests from GitLab", - scopes=guild_ids, ) @slash_option( name="state", @@ -386,11 +378,9 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="milestones", sub_cmd_description="Get milestones from GitLab", - scopes=guild_ids, ) async def _milestones(self, ctx: InteractionContext) -> None: await ctx.defer() diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index b03271b..cc7ea52 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -16,6 +16,7 @@ from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, slash_command, slash_option, ) @@ -205,7 +206,9 @@ class RemindmeCog(Scale): return embed - @slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders") + reminders = SlashCommand(name="reminders", description="Manage reminders") + + @reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders") async def _list(self, ctx: InteractionContext) -> None: reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: @@ -216,7 +219,7 @@ class RemindmeCog(Scale): await ctx.send(embed=embed) - @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") + @reminders.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: @@ -303,8 +306,7 @@ class RemindmeCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="reminders", + @reminders.subcommand( sub_cmd_name="fetch", sub_cmd_description="Fetch a reminder that failed to send", ) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 7269f3d..6ab9abe 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check, cooldown @@ -28,9 +28,9 @@ class RolegiverCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="rolegiver", - description="Allow users to choose their own roles", + rolegiver = SlashCommand(name="rolegiver", description="Allow users to choose their own roles") + + @rolegiver.subcommand( sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver", ) @@ -82,9 +82,7 @@ class RolegiverCog(Scale): await ctx.send(embed=embed) - @slash_command( - name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver" - ) + @rolegiver.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_remove(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) @@ -167,7 +165,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles") + @rolegiver.subcommand(sub_cmd_name="list", description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): @@ -202,7 +200,9 @@ class RolegiverCog(Scale): await ctx.send(embed=embed) - @slash_command(name="role", sub_cmd_name="get", sub_cmd_description="Get a role") + role = SlashCommand(name="role", description="Get/Remove Rolegiver roles") + + @role.subcommand(sub_cmd_name="get", sub_cmd_description="Get a role") @cooldown(bucket=Buckets.USER, rate=1, interval=10) async def _role_get(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) @@ -278,7 +278,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @role.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role") @cooldown(bucket=Buckets.USER, rate=1, interval=10) async def _role_remove(self, ctx: InteractionContext) -> None: user_roles = ctx.author.roles @@ -357,8 +357,8 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" + @rolegiver.subcommand( + sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 29afb7d..88cb3c5 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -4,22 +4,19 @@ from typing import Any from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions +from dis_snek.models.discord.role import Role from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check -from discord import Role, TextChannel -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option from jarvis_core.db import q +from jarvis_core.db.models import Setting -from jarvis.db.models import Setting from jarvis.utils import build_embed -from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions @@ -47,199 +44,175 @@ class SettingsCog(Scale): return await existing.delete() return False - @slash_command( - name="settings", - description="Control bot settings", + settings = SlashCommand(name="settings", description="Control guild settings") + set_ = settings.group(name="set", description="Set a setting") + unset = settings.group(name="unset", description="Unset a setting") + + @set_.subcommand( sub_cmd_name="modlog", - sub_cmd_description="Set ActivityLog channel", + sub_cmd_description="Set Moglod channel", ) @slash_option( name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _set_modlog(self, ctx: InteractionContext(), channel: GuildText) -> None: + async def _set_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: if not isinstance(channel, GuildText): await ctx.send("Channel must be a GuildText", ephemeral=True) return - self.update_settings("modlog", channel.id, ctx.guild.id) + await self.update_settings("modlog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New modlog channel is {channel.mention}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="activitylog", - description="Set activitylog channel", - choices=[ - create_option( - name="channel", - description="Activitylog channel", - opt_type=7, - required=True, - ) - ], + @set_.subcommand( + sub_cmd_name="activitylog", + sub_cmd_description="Set Activitylog channel", + ) + @slash_option( + name="channel", + description="Activitylog Channel", + opt_type=OptionTypes.CHANNEL, + required=True, ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", ephemeral=True) + async def _set_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText", ephemeral=True) return - self.update_settings("activitylog", channel.id, ctx.guild.id) + await self.update_settings("activitylog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New activitylog channel is {channel.mention}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="massmention", - description="Set massmention amount", - choices=[ - create_option( - name="amount", - description="Amount of mentions (0 to disable)", - opt_type=4, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") + @slash_option( + name="amount", + description="Amount of mentions (0 to disable)", + opt_type=OptionTypes.INTEGER, + required=True, ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_massmention(self, ctx: SlashContext, amount: int) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_massmention(self, ctx: InteractionContext, amount: int) -> None: await ctx.defer() - self.update_settings("massmention", amount, ctx.guild.id) + await self.update_settings("massmention", amount, ctx.guild.id) await ctx.send(f"Settings applied. New massmention limit is {amount}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="verified", - description="Set verified role", - choices=[ - create_option( - name="role", - description="verified role", - opt_type=8, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @slash_option( + name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_verified(self, ctx: SlashContext, role: Role) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_verified(self, ctx: InteractionContext, role: Role) -> None: await ctx.defer() - self.update_settings("verified", role.id, ctx.guild.id) + await self.update_settings("verified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New verified role is `{role.name}`") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="unverified", - description="Set unverified role", - choices=[ - create_option( - name="role", - description="Unverified role", - opt_type=8, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") + @slash_option( + name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_unverified(self, ctx: SlashContext, role: Role) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_unverified(self, ctx: InteractionContext, role: Role) -> None: await ctx.defer() - self.update_settings("unverified", role.id, ctx.guild.id) + await self.update_settings("unverified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New unverified role is `{role.name}`") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="noinvite", - description="Set if invite deletion should happen", - choices=[ - create_option( - name="active", - description="Active?", - opt_type=OptionTypes.BOOLEAN, - required=True, - ) - ], + @set_.subcommand( + sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_invitedel(self, ctx: SlashContext, active: bool) -> None: + @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() - self.update_settings("noinvite", active, ctx.guild.id) + await self.update_settings("noinvite", active, ctx.guild.id) await ctx.send(f"Settings applied. Automatic invite active: {active}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="modlog", - description="Unset modlog channel", + # Unset + @unset.subcommand( + sub_cmd_name="modlog", + sub_cmd_description="Set Moglod channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _unset_modlog(self, ctx: SlashContext) -> None: - self.delete_settings("modlog", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="activitylog", - description="Unset activitylog channel", + @slash_option( + name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _unset_activitylog(self, ctx: SlashContext) -> None: - self.delete_settings("activitylog", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="massmention", - description="Unet massmention amount", - ) - @check(admin_or_permissions(manage_guild=True)) - async def _massmention(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: await ctx.defer() - self.delete_settings("massmention", ctx.guild.id) - await ctx.send("Setting removed.") + await self.delete_settings("modlog", channel.id, ctx.guild.id) + await ctx.send(f"Setting `{channel.mention}` unset") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="verified", - description="Unset verified role", + @unset.subcommand( + sub_cmd_name="activitylog", + sub_cmd_description="Set Activitylog channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _verified(self, ctx: SlashContext) -> None: - await ctx.defer() - self.delete_settings("verified", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="unverified", - description="Unset unverified role", + @slash_option( + name="channel", + description="Activitylog Channel", + opt_type=OptionTypes.CHANNEL, + required=True, ) - @check(admin_or_permissions(manage_guild=True)) - async def _unverified(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: await ctx.defer() - self.delete_settings("unverified", ctx.guild.id) - await ctx.send("Setting removed.") + await self.delete_settings("activitylog", channel.id, ctx.guild.id) + await ctx.send(f"Setting `{channel.mention}` unset") - @cog_ext.cog_subcommand(base="settings", name="view", description="View settings") - @check(admin_or_permissions(manage_guild=True)) - async def _view(self, ctx: SlashContext) -> None: - settings = Setting.objects(guild=ctx.guild.id) + @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") + @slash_option( + name="amount", + description="Amount of mentions (0 to disable)", + opt_type=OptionTypes.INTEGER, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_massmention(self, ctx: InteractionContext, amount: int) -> None: + await ctx.defer() + await self.delete_settings("massmention") + await ctx.send(f"Setting `{amount}` unset") + + @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @slash_option( + name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_verified(self, ctx: InteractionContext, role: Role) -> None: + await ctx.defer() + await self.delete_settings("verified") + await ctx.send(f"Setting `{role.name} unset`") + + @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") + @slash_option( + name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_unverified(self, ctx: InteractionContext, role: Role) -> None: + await ctx.defer() + await self.delete_settings("unverified") + await ctx.send(f"Setting `{role.name}` unset") + + @unset.subcommand( + sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" + ) + @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: + await ctx.defer() + await self.delete_settings("noinvite") + await ctx.send(f"Setting `{active}` unset") + + @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _view(self, ctx: InteractionContext) -> None: + settings = Setting.find(q(guild=ctx.guild.id)) fields = [] - for setting in settings: + async for setting in settings: value = setting.value if setting.setting in ["unverified", "verified", "mute"]: - value = find(lambda x: x.id == value, ctx.guild.roles) + value = await ctx.guild.fetch_role(value) if value: value = value.mention else: value = "||`[redacted]`||" elif setting.setting in ["activitylog", "modlog"]: - value = find(lambda x: x.id == value, ctx.guild.text_channels) + value = await ctx.guild.fetch_channel(value) if value: value = value.mention else: @@ -247,22 +220,23 @@ class SettingsCog(Scale): elif setting.setting == "rolegiver": value = "" for _role in setting.value: - nvalue = find(lambda x: x.id == value, ctx.guild.roles) + nvalue = await ctx.guild.fetch_role(value) if value: value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(Field(name=setting.setting, value=value or "N/A")) + fields.append(EmbedField(name=setting.setting, value=value or "N/A", inlilne=False)) embed = build_embed(title="Current Settings", description="", fields=fields) await ctx.send(embed=embed) - @cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings") - @check(admin_or_permissions(manage_guild=True)) - async def _clear(self, ctx: SlashContext) -> None: - deleted = Setting.objects(guild=ctx.guild.id).delete() - await ctx.send(f"Guild settings cleared: `{deleted is not None}`") + @settings.subcommand(sub_cmd_name="clear", sub_cmd_description="Clear all settings") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _clear(self, ctx: InteractionContext) -> None: + async for setting in Setting.find(q(guild=ctx.guild.id)): + await setting.delete() + await ctx.send("Guild settings cleared") def setup(bot: Snake) -> None: diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index b74afb4..b0bfccf 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -9,8 +9,8 @@ from dis_snek.models.discord.message import Message from dis_snek.models.snek.application_commands import ( CommandTypes, OptionTypes, + SlashCommand, context_menu, - slash_command, slash_option, ) from dis_snek.models.snek.command import check @@ -36,9 +36,9 @@ class StarboardCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="starboard", - description="Extra pins! Manage starboards", + starboard = SlashCommand(name="starboard", description="Extra pins! Manage starboards") + + @starboard.subcommand( sub_cmd_name="list", sub_cmd_description="List all starboards", ) @@ -53,9 +53,7 @@ class StarboardCog(Scale): else: await ctx.send("No Starboards available.") - @slash_command( - name="starboard", sub_cmd_name="create", sub_cmd_description="Create a starboard" - ) + @starboard.subcommand(sub_cmd_name="create", sub_cmd_description="Create a starboard") @slash_option( name="channel", description="Starboard channel", @@ -91,9 +89,7 @@ class StarboardCog(Scale): ).commit() await ctx.send(f"Starboard created. Check it out at {channel.mention}.") - @slash_command( - name="starboard", sub_cmd_name="delete", sub_cmd_description="Delete a starboard" - ) + @starboard.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a starboard") @slash_option( name="channel", description="Starboard channel", @@ -229,9 +225,12 @@ class StarboardCog(Scale): async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add(ctx, message=str(ctx.target_id)) - @slash_command( + star = SlashCommand( name="star", description="Manage stars", + ) + + @star.subcommand( sub_cmd_name="add", sub_cmd_description="Star a message", ) @@ -250,9 +249,7 @@ class StarboardCog(Scale): ) -> None: await self._star_add(ctx, message, channel) - @slash_command( - name="star", sub_cmd_name="delete", sub_cmd_description="Delete a starred message" - ) + @star.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a starred message") @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True ) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index aec012f..d3e7af4 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -34,9 +34,12 @@ class TwitterCog(Scale): self._guild_cache = {} self._channel_cache = {} - @slash_command( + twitter = SlashCommand( name="twitter", description="Manage Twitter follows", + ) + + @twitter.subcommand( sub_cmd_name="follow", sub_cmd_description="Follow a Twitter acount", ) @@ -108,9 +111,7 @@ class TwitterCog(Scale): await ctx.send(f"Now following `@{handle}` in {channel.mention}") - @slash_command( - name="twitter", sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts" - ) + @twitter.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) @@ -164,8 +165,7 @@ class TwitterCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="twitter", + @twitter.subcommand( sub_cmd_name="retweets", sub_cmd_description="Modify followed Twitter accounts", ) From 72d91e6bbd8eed5751b3fa13c6c75376a61151be Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:02:10 -0600 Subject: [PATCH 29/95] Fix syntax errors --- jarvis/cogs/rolegiver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 6ab9abe..878ece4 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -165,7 +165,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @rolegiver.subcommand(sub_cmd_name="list", description="List rolegiver roles") + @rolegiver.subcommand(sub_cmd_name="list", sub_cmd_description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): @@ -358,7 +358,7 @@ class RolegiverCog(Scale): await message.edit(components=components) @rolegiver.subcommand( - sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" + sub_cmd_name="cleanup", sub_cmd_description="Removed deleted roles from rolegiver" ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: From c2c94a71d68f6f426932bbaf9efe8c771d6ef515 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:18:17 -0600 Subject: [PATCH 30/95] More debug logs --- jarvis/client.py | 6 ++++++ jarvis/cogs/admin/__init__.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 04d44bb..01e2cf5 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -53,6 +53,7 @@ class Jarvis(Snake): super().__init__(*args, **kwargs) self.logger = logging.getLogger(__name__) self.phishing_domains = [] + self.pre_run_callback = self._prerun @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: @@ -72,6 +73,11 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) + async def _prerun(self, ctx: Context, *args, **kwargs) -> None: + name = ctx.invoked_name + args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) + self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") + async def _sync_domains(self) -> None: self.logger.debug("Loading phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index daaca31..2022554 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. Admin Cogs.""" +import logging + from dis_snek import Snake from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning @@ -6,11 +8,21 @@ from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, def setup(bot: Snake) -> None: """Add admin cogs to J.A.R.V.I.S.""" + logger = logging.getLogger(__name__) + msg = "Loaded jarvis.cogs.admin.{}" ban.BanCog(bot) + logger.debug(msg.format("ban")) kick.KickCog(bot) + logger.debug(msg.format("kick")) lock.LockCog(bot) + logger.debug(msg.format("lock")) lockdown.LockdownCog(bot) + logger.debug(msg.format("ban")) mute.MuteCog(bot) + logger.debug(msg.format("mute")) purge.PurgeCog(bot) + logger.debug(msg.format("purge")) roleping.RolepingCog(bot) + logger.debug(msg.format("roleping")) warning.WarningCog(bot) + logger.debug(msg.format("warning")) From 002bf5b1506d46b85858d032ca740c93300b23aa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:19:31 -0600 Subject: [PATCH 31/95] Fix missing option in uuid2ulid and ulid2uuid --- jarvis/cogs/dev.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f746d23..f08bf22 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -168,6 +168,9 @@ class DevCog(Scale): name="uuid2ulid", description="Convert a UUID to a ULID", ) + @slash_option( + name="uuid", description="UUID to convert", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None: if UUID_VERIFY.match(uuid): @@ -180,6 +183,9 @@ class DevCog(Scale): name="ulid2uuid", description="Convert a ULID to a UUID", ) + @slash_option( + name="ulid", description="ULID to convert", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None: if ULID_VERIFY.match(ulid): From 029743f9770e0d631e47068c232254b6ab7ef5a7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:32:24 -0600 Subject: [PATCH 32/95] Lots more event debugging --- jarvis/client.py | 216 ++++++++++++++++++++++++++--------------------- 1 file changed, 121 insertions(+), 95 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 01e2cf5..b0b48eb 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -102,6 +102,7 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" + self.logger.debug(f"Handling error in {ctx.invoked_name}: {error}") if isinstance(error, CommandOnCooldown): await ctx.send(str(error), ephemeral=True) return @@ -191,7 +192,8 @@ class Jarvis(Snake): guild = user.guild unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: - role = guild.get_role(unverified.value) + self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}") + role = await guild.fetch_role(unverified.value) if role not in user.roles: await user.add_role(role, reason="User just joined and is unverified") @@ -199,6 +201,9 @@ class Jarvis(Snake): """Handle autopurge events.""" autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) if autopurge: + self.logger.debug( + f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}" + ) await message.delete(delay=autopurge.delay) async def autoreact(self, message: Message) -> None: @@ -210,6 +215,9 @@ class Jarvis(Snake): ) ) if autoreact: + self.logger.debug( + f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}" + ) for reaction in autoreact.reactions: await message.add_reaction(reaction) if autoreact.thread: @@ -240,17 +248,17 @@ class Jarvis(Snake): "VtgZntXcnZ", "gPfYGbvTCE", ] - if match.group(1) not in allowed and setting.value: + if (m := match.group(1)) not in allowed and setting.value: + self.logger.debug(f"Removing non-allowed invite {m} from {message.guild.id}") await message.delete() - w = Warning( + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Sent an invite link", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Sent an invite link") await message.channel.send(embed=embed) @@ -270,15 +278,17 @@ class Jarvis(Snake): - (1 if message.author.id in message._mention_ids else 0) # noqa: W503 > massmention.value # noqa: W503 ): - w = Warning( + self.logger.debug( + f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Mass Mention", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Mass Mention") await message.channel.send(embed=embed) @@ -322,31 +332,35 @@ class Jarvis(Snake): break if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = Warning( + self.logger.debug( + f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Pinged a blocked role/user with a blocked role", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role") await message.channel.send(embed=embed) async def phishing(self, message: Message) -> None: """Check if the message contains any known phishing domains.""" for match in url.finditer(message.content): - if match.group("domain") in self.phishing_domains: - w = Warning( + if (m := match.group("domain")) in self.phishing_domains: + self.logger.debug( + f"Phishing url {m} detected in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Phishing URL", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Phishing URL") await message.channel.send(embed=embed) await message.delete() @@ -365,15 +379,17 @@ class Jarvis(Snake): data = await resp.json() for item in data["processed"]["urls"].values(): if not item["safe"]: - w = Warning( + self.logger.debug( + f"Phishing url {match.string} detected in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Unsafe URL", user=message.author.id, - ) - await w.commit() + ).commit() reasons = ", ".join(item["not_safe_reasons"]) embed = warning_embed(message.author, reasons) await message.channel.send(embed=embed) @@ -404,36 +420,41 @@ class Jarvis(Snake): if modlog: if not before or before.content == after.content or before.content is None: return - channel = before.guild.get_channel(modlog.value) - fields = [ - EmbedField( - "Original Message", - before.content if before.content else "N/A", - False, - ), - EmbedField( - "New Message", - after.content if after.content else "N/A", - False, - ), - ] - embed = build_embed( - title="Message Edited", - description=f"{after.author.mention} edited a message", - fields=fields, - color="#fc9e3f", - timestamp=after.edited_timestamp, - url=after.jump_url, - ) - embed.set_author( - name=after.author.username, - icon_url=after.author.display_avatar.url, - url=after.jump_url, - ) - embed.set_footer( - text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" - ) - await channel.send(embed=embed) + try: + channel = before.guild.get_channel(modlog.value) + fields = [ + EmbedField( + "Original Message", + before.content if before.content else "N/A", + False, + ), + EmbedField( + "New Message", + after.content if after.content else "N/A", + False, + ), + ] + embed = build_embed( + title="Message Edited", + description=f"{after.author.mention} edited a message", + fields=fields, + color="#fc9e3f", + timestamp=after.edited_timestamp, + url=after.jump_url, + ) + embed.set_author( + name=after.author.username, + icon_url=after.author.display_avatar.url, + url=after.jump_url, + ) + embed.set_footer( + text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" + ) + await channel.send(embed=embed) + except Exception as e: + self.logger.warn( + f"Failed to process edit {before.guild.id}/{before.channel.id}/{before.id}: {e}" + ) if not isinstance(after.channel, DMChannel) and not after.author.bot: await self.massmention(after) await self.roleping(after) @@ -455,53 +476,58 @@ class Jarvis(Snake): content = "N/A" fields = [EmbedField("Original Message", content, False)] - if message.attachments: - value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) - fields.append( - EmbedField( - name="Attachments", - value=value, - inline=False, + try: + if message.attachments: + value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) + fields.append( + EmbedField( + name="Attachments", + value=value, + inline=False, + ) + ) + + if message.sticker_items: + value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) + fields.append( + EmbedField( + name="Stickers", + value=value, + inline=False, + ) + ) + + if message.embeds: + value = str(len(message.embeds)) + " embeds" + fields.append( + EmbedField( + name="Embeds", + value=value, + inline=False, + ) + ) + + channel = message.guild.get_channel(modlog.value) + embed = build_embed( + title="Message Deleted", + description=f"{message.author.mention}'s message was deleted", + fields=fields, + color="#fc9e3f", + ) + + embed.set_author( + name=message.author.username, + icon_url=message.author.display_avatar.url, + url=message.jump_url, + ) + embed.set_footer( + text=( + f"{message.author.username}#{message.author.discriminator} | " + f"{message.author.id}" ) ) - - if message.sticker_items: - value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) - fields.append( - EmbedField( - name="Stickers", - value=value, - inline=False, - ) + await channel.send(embed=embed) + except Exception as e: + self.logger.warn( + f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}" ) - - if message.embeds: - value = str(len(message.embeds)) + " embeds" - fields.append( - EmbedField( - name="Embeds", - value=value, - inline=False, - ) - ) - - channel = message.guild.get_channel(modlog.value) - embed = build_embed( - title="Message Deleted", - description=f"{message.author.mention}'s message was deleted", - fields=fields, - color="#fc9e3f", - ) - - embed.set_author( - name=message.author.username, - icon_url=message.author.display_avatar.url, - url=message.jump_url, - ) - embed.set_footer( - text=( - f"{message.author.username}#{message.author.discriminator} | " - f"{message.author.id}" - ) - ) - await channel.send(embed=embed) From 3b9a3f721b7c8fd65451b91dd6377e4a01993fc4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:40:22 -0600 Subject: [PATCH 33/95] Fix errors in client event processing --- jarvis/client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index b0b48eb..85c9e3e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -241,16 +241,20 @@ class Jarvis(Snake): setting = Setting(guild=message.guild.id, setting="noinvite", value=True) await setting.commit() if match: - guild_invites = await message.guild.invites() - guild_invites.append(message.guild.vanity_url_code) + guild_invites = await message.guild.fetch_invites() + if message.guild.vanity_url_code: + guild_invites.append(message.guild.vanity_url_code) allowed = [x.code for x in guild_invites] + [ "dbrand", "VtgZntXcnZ", "gPfYGbvTCE", ] if (m := match.group(1)) not in allowed and setting.value: - self.logger.debug(f"Removing non-allowed invite {m} from {message.guild.id}") - await message.delete() + self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}") + try: + await message.delete() + except Exception: + self.logger.debug("Message deleted before action taken") await Warning( active=True, admin=self.user.id, @@ -351,7 +355,7 @@ class Jarvis(Snake): for match in url.finditer(message.content): if (m := match.group("domain")) in self.phishing_domains: self.logger.debug( - f"Phishing url {m} detected in {message.guild.id}/{message.channel.id}/{message.id}" + f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) await Warning( active=True, @@ -380,7 +384,7 @@ class Jarvis(Snake): for item in data["processed"]["urls"].values(): if not item["safe"]: self.logger.debug( - f"Phishing url {match.string} detected in {message.guild.id}/{message.channel.id}/{message.id}" + f"Scam url `{match.string}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) await Warning( active=True, From 46693f2443fb9fe7c1fbe52b5274e887424d98a7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 00:47:38 -0600 Subject: [PATCH 34/95] Make dateparser timezone aware --- jarvis/cogs/remindme.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index cc7ea52..0a7a0c3 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -102,7 +102,7 @@ class RemindmeCog(Scale): base_settings = { "PREFER_DATES_FROM": "future", "TIMEZONE": "UTC", - "RETURN_AS_TIMEZONE_AWARE": False, + "RETURN_AS_TIMEZONE_AWARE": True, } rt_settings = base_settings.copy() rt_settings["PARSERS"] = [ @@ -115,12 +115,11 @@ class RemindmeCog(Scale): at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] at_remind_at = parse(delay, settings=at_settings) - if rt_remind_at and at_remind_at: - remind_at = max(rt_remind_at, at_remind_at) + if rt_remind_at: + remind_at = rt_remind_at + elif at_remind_at: + remind_at = at_remind_at else: - remind_at = rt_remind_at or at_remind_at - - if not remind_at: self.logger.debug(f"Failed to parse delay: {delay}") await response.send( f"`{delay}` is not a parsable date, please try again", ephemeral=True From 3100ea1f62be8ba54832f4a9ef1b8a539e6c6a1d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 02:24:32 -0600 Subject: [PATCH 35/95] Add `/timestamp` --- jarvis/cogs/util.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 61b3da2..d2dd557 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -4,9 +4,11 @@ import platform import re import secrets import string +from datetime import timezone from io import BytesIO import numpy as np +from dateparser import parse from dis_snek import InteractionContext, Scale, Snake, const from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice from dis_snek.models.discord.embed import EmbedField @@ -23,6 +25,7 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from PIL import Image +from tzlocal import get_localzone import jarvis from jarvis.data import pigpen @@ -296,6 +299,7 @@ class UtilCog(Scale): if length > 256: await ctx.send("Please limit password to 256 characters", ephemeral=True) return + choices = [ string.ascii_letters, string.hexdigits, @@ -329,6 +333,35 @@ class UtilCog(Scale): outp += "`" await ctx.send(outp[:2000]) + @slash_command( + name="timestamp", description="Convert a datetime or timestamp into it's counterpart" + ) + @slash_option( + name="string", description="String to convert", opt_type=OptionTypes.STRING, required=True + ) + async def _timestamp(self, ctx: InteractionContext, string: str) -> None: + timestamp = parse(string) + if not timestamp: + await ctx.send("Valid time not found, try again", ephemeral=True) + return + + if not timestamp.tzinfo: + timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(tz=timezone.utc) + + timestamp_utc = timestamp.astimezone(tz=timezone.utc) + + ts = int(timestamp.timestamp()) + ts_utc = int(timestamp_utc.timestamp()) + fields = [ + EmbedField(name="Unix Epoch", value=f"`{ts}`"), + EmbedField(name="Unix Epoch (UTC)", value=f"`{ts_utc}`"), + EmbedField(name="Absolute Time", value=f"\n``"), + EmbedField(name="Relative Time", value=f"\n``"), + EmbedField(name="ISO8601", value=timestamp.isoformat()), + ] + embed = build_embed(title="Converted Time", description="", fields=fields) + await ctx.send(embed=embed) + def setup(bot: Snake) -> None: """Add UtilCog to J.A.R.V.I.S.""" From 9ae61247a0c88e31c913537667d9e3d30865c593 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 02:41:03 -0600 Subject: [PATCH 36/95] Add description to timestamp embed --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index d2dd557..3964a00 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -359,7 +359,7 @@ class UtilCog(Scale): EmbedField(name="Relative Time", value=f"\n``"), EmbedField(name="ISO8601", value=timestamp.isoformat()), ] - embed = build_embed(title="Converted Time", description="", fields=fields) + embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields) await ctx.send(embed=embed) From 2a42ae956c3042088ab759b39cf8b876bc17d98a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 28 Mar 2022 18:15:29 -0600 Subject: [PATCH 37/95] Add `private` flag to /timestamp --- jarvis/cogs/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 3964a00..4cda0c9 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -339,7 +339,10 @@ class UtilCog(Scale): @slash_option( name="string", description="String to convert", opt_type=OptionTypes.STRING, required=True ) - async def _timestamp(self, ctx: InteractionContext, string: str) -> None: + @slash_option( + name="private", description="Respond quietly?", opt_type=OptionTypes.BOOLEAN, required=False + ) + async def _timestamp(self, ctx: InteractionContext, string: str, private: bool = False) -> None: timestamp = parse(string) if not timestamp: await ctx.send("Valid time not found, try again", ephemeral=True) @@ -360,7 +363,7 @@ class UtilCog(Scale): EmbedField(name="ISO8601", value=timestamp.isoformat()), ] embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields) - await ctx.send(embed=embed) + await ctx.send(embed=embed, ephemeral=private) def setup(bot: Snake) -> None: From 047cf075f25dadb261716250ebc13620e6159f2b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 28 Mar 2022 18:15:49 -0600 Subject: [PATCH 38/95] Add channel context to a few activitylog actions --- jarvis/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 85c9e3e..591fba8 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -176,7 +176,7 @@ class Jarvis(Snake): ] embed = build_embed( title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", + description=f"{ctx.author.mention} invoked a command in {ctx.channel.mention}", fields=fields, color="#fc9e3f", ) @@ -440,7 +440,7 @@ class Jarvis(Snake): ] embed = build_embed( title="Message Edited", - description=f"{after.author.mention} edited a message", + description=f"{after.author.mention} edited a message in {before.channel.mention}", fields=fields, color="#fc9e3f", timestamp=after.edited_timestamp, @@ -514,7 +514,7 @@ class Jarvis(Snake): channel = message.guild.get_channel(modlog.value) embed = build_embed( title="Message Deleted", - description=f"{message.author.mention}'s message was deleted", + description=f"{message.author.mention}'s message was deleted from {message.channel.mention}", fields=fields, color="#fc9e3f", ) From ff71e937206a436e9f3ed4576cf74d086078e947 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 15:30:09 -0600 Subject: [PATCH 39/95] Fix reminders display in /reminders delete --- .pre-commit-config.yaml | 2 +- jarvis/cogs/remindme.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 045ddd0..a2e9a36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: python-check-blanket-noqa - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black args: [--line-length=100, --target-version=py310] diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 0a7a0c3..23b09cf 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -228,7 +228,7 @@ class RemindmeCog(Scale): options = [] for reminder in reminders: option = SelectOption( - label=f"", + label=f"{reminder.remind_at}", value=str(reminder.id), emoji="⏰", ) From f0502d65db34d85b03b62d376d1eac081f7a0d6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 22:11:33 -0600 Subject: [PATCH 40/95] Mostly fix CTC2 --- jarvis/cogs/ctc2.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 31658a9..a007628 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -7,7 +7,11 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User -from dis_snek.models.snek.application_commands import SlashCommand +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -15,7 +19,7 @@ from jarvis_core.db.models import Guess from jarvis.utils import build_embed -guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] +guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862402786116763668] valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") invites = re.compile( @@ -36,7 +40,7 @@ class CTCCog(Scale): def __del__(self): self._session.close() - ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopres=guild_ids) + ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids) @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) @@ -47,6 +51,9 @@ class CTCCog(Scale): sub_cmd_name="pw", sub_cmd_description="Guess a password for https://completethecodetwo.cards", ) + @slash_option( + name="guess", description="Guess a password", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _pw(self, ctx: InteractionContext, guess: str) -> None: if len(guess) > 800: @@ -85,7 +92,7 @@ class CTCCog(Scale): correct = True else: await ctx.send("Nope.", ephemeral=True) - _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() + await Guess(guess=guess, user=ctx.author.id, correct=correct).commit() @ctc2.subcommand( sub_cmd_name="guesses", @@ -93,9 +100,10 @@ class CTCCog(Scale): ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: - guesses = Guess.objects().order_by("-correct", "-id") + await ctx.defer() + guesses = Guess.find().sort("correct", -1).sort("id", -1) fields = [] - for guess in guesses: + async for guess in guesses: user = await ctx.guild.get_member(guess["user"]) if not user: user = await self.bot.fetch_user(guess["user"]) From 2b11fea0cbcddff58ee0ce0527ee988d69c57b62 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 22:12:05 -0600 Subject: [PATCH 41/95] Work on context menu muting --- jarvis/__init__.py | 4 +- jarvis/cogs/admin/mute.py | 122 +++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 425519e..34f5af8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -16,7 +16,6 @@ except Exception: __version__ = "0.0.0" jconfig = JarvisConfig.from_yaml() - logger = get_logger("jarvis") logger.setLevel(jconfig.log_level) file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") @@ -28,8 +27,7 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None - -jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) +jarvis = Jarvis(intents=intents, sync_interactions=jconfig.sync) async def run() -> None: diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 4ccf7f1..7cc022b 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,13 +1,19 @@ """J.A.R.V.I.S. MuteCog.""" +import asyncio import logging from datetime import datetime, timedelta, timezone +from dateparser import parse +from dateparser_data.settings import default_parsers from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( + CommandTypes, OptionTypes, SlashCommandChoice, + context_menu, slash_command, slash_option, ) @@ -26,6 +32,96 @@ class MuteCog(ModcaseCog): super().__init__(bot) self.logger = logging.getLogger(__name__) + async def _apply_timeout( + self, ctx: InteractionContext, user: Member, reason: str, until: datetime + ) -> None: + await user.timeout(communication_disabled_until=until, reason=reason) + duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60) + await Mute( + user=user.id, + reason=reason, + admin=ctx.author.id, + guild=ctx.guild.id, + duration=duration, + active=True, + ).commit() + ts = int(until.timestamp()) + + embed = build_embed( + title="User Muted", + description=f"{user.mention} has been muted", + fields=[ + EmbedField(name="Reason", value=reason), + EmbedField(name="Until", value=f" "), + ], + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_thumbnail(url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + return embed + + @context_menu(name="Mute User", context_type=CommandTypes.USER) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _timeout_cm(self, ctx: InteractionContext) -> None: + modal = Modal( + title=f"Muting {ctx.target.mention}", + components=[ + InputText( + label="Reason?", + placeholder="Spamming, harrassment, etc", + style=TextStyles.SHORT, + custom_id="reason", + max_length=100, + ), + InputText( + label="Duration", + placeholder="1h 30m | in 5 minutes | in 4 weeks", + style=TextStyles.SHORT, + custom_id="until", + max_length=100, + ), + ], + ) + await ctx.send_modal(modal) + try: + response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + reason = response.responses.get("reason") + until = response.responses.get("until") + except asyncio.TimeoutError: + return + base_settings = { + "PREFER_DATES_FROM": "future", + "TIMEZONE": "UTC", + "RETURN_AS_TIMEZONE_AWARE": True, + } + rt_settings = base_settings.copy() + rt_settings["PARSERS"] = [ + x for x in default_parsers if x not in ["absolute-time", "timestamp"] + ] + + rt_until = parse(until, settings=rt_settings) + + at_settings = base_settings.copy() + at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] + at_until = parse(until, settings=at_settings) + + if rt_until: + until = rt_until + elif at_until: + until = at_until + else: + self.logger.debug(f"Failed to parse delay: {until}") + await response.send( + f"`{until}` is not a parsable date, please try again", ephemeral=True + ) + return + embed = await self._apply_timeout(ctx, ctx.target, reason, until) + await response.send(embed=embed) + @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -77,29 +173,7 @@ class MuteCog(ModcaseCog): return until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) - await user.timeout(communication_disabled_until=until, reason=reason) - m = Mute( - user=user.id, - reason=reason, - admin=ctx.author.id, - guild=ctx.guild.id, - duration=duration, - active=True, - ) - await m.commit() - ts = int(until.timestamp()) - - embed = build_embed( - title="User Muted", - description=f"{user.mention} has been muted", - fields=[ - EmbedField(name="Reason", value=reason), - EmbedField(name="Until", value=f" "), - ], - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_thumbnail(url=user.display_avatar.url) - embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + embed = await self._apply_timeout(ctx, user, reason, until) await ctx.send(embed=embed) @slash_command(name="unmute", description="Unmute a user") @@ -120,6 +194,8 @@ class MuteCog(ModcaseCog): await ctx.send("User is not muted", ephemeral=True) return + await user.timeout(communication_disabled_until=0) + embed = build_embed( title="User Unmuted", description=f"{user.mention} has been unmuted", From 278f8b07905579514ee802ce94646981305987fe Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 09:50:52 -0600 Subject: [PATCH 42/95] Fix paste error --- jarvis/client.py | 6 +++--- poetry.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 591fba8..bae626e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -133,10 +133,10 @@ class Jarvis(Snake): ) error_message = "".join(traceback.format_exception(error)) if len(full_message + error_message) >= 1800: - error_message = " ".join(error_message.split("\n")) + error_message = "\n ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message - paste = Paste(content=full_message) - await paste.save(DEFAULT_SITE) + paste = Paste(content=full_message, site=DEFAULT_SITE) + await paste.save() self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") await channel.send( diff --git a/poetry.lock b/poetry.lock index c50d2db..a3067a7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -622,7 +622,7 @@ test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] [[package]] name = "python-lsp-server" -version = "1.4.0" +version = "1.4.1" description = "Python Language Server for the Language Server Protocol" category = "dev" optional = false @@ -1608,8 +1608,8 @@ python-lsp-jsonrpc = [ {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, ] python-lsp-server = [ - {file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"}, - {file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"}, + {file = "python-lsp-server-1.4.1.tar.gz", hash = "sha256:be7f83298af9f0951a93972cafc9db04fd7cf5c05f20812515275f0ba70e342f"}, + {file = "python_lsp_server-1.4.1-py3-none-any.whl", hash = "sha256:e6bd0cf9530d8e2ba3b9c0ae10b99a16c33808bc9a7266afecc3900017b35326"}, ] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, From 5e51a9a20331c93887e9f514091d56014935d523 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:12:27 -0600 Subject: [PATCH 43/95] Fix unmute, past dates on mute --- jarvis/cogs/admin/mute.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 7cc022b..3ad3a56 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -109,6 +109,7 @@ class MuteCog(ModcaseCog): at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] at_until = parse(until, settings=at_settings) + old_until = until if rt_until: until = rt_until elif at_until: @@ -119,6 +120,11 @@ class MuteCog(ModcaseCog): f"`{until}` is not a parsable date, please try again", ephemeral=True ) return + if until < datetime.now(tz=timezone.utc): + await response.send( + f"`{old_until}` is in the past, which isn't allowed", ephemeral=True + ) + return embed = await self._apply_timeout(ctx, ctx.target, reason, until) await response.send(embed=embed) @@ -194,7 +200,7 @@ class MuteCog(ModcaseCog): await ctx.send("User is not muted", ephemeral=True) return - await user.timeout(communication_disabled_until=0) + await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc)) embed = build_embed( title="User Unmuted", From 87e3b18a77cd303f20bdf298e55c5305d52f1f11 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:13:18 -0600 Subject: [PATCH 44/95] Better command logging --- jarvis/client.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index bae626e..410dc17 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -73,8 +73,10 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) - async def _prerun(self, ctx: Context, *args, **kwargs) -> None: + async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: name = ctx.invoked_name + if ctx.target: + kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") @@ -115,6 +117,8 @@ class Jarvis(Snake): timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" + if ctx.target: + ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " if isinstance(v, str) and len(v) > 100: @@ -136,8 +140,8 @@ class Jarvis(Snake): error_message = "\n ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message paste = Paste(content=full_message, site=DEFAULT_SITE) - await paste.save() - self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") + key = await paste.save() + self.logger.debug(f"Large traceback, saved to Pasty {paste.id}, {key=}") await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." @@ -160,6 +164,8 @@ class Jarvis(Snake): if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] + if ctx.target: + args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): v = v.replace("`", "\\`") From 989d8daff874f4c1daf1f85409e92821e2df2060 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:20:16 -0600 Subject: [PATCH 45/95] Fix target_id check in logs --- jarvis/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 410dc17..bb90256 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -75,7 +75,7 @@ class Jarvis(Snake): async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: name = ctx.invoked_name - if ctx.target: + if ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") @@ -117,7 +117,7 @@ class Jarvis(Snake): timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" - if ctx.target: + if ctx.target_id: ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " @@ -164,7 +164,7 @@ class Jarvis(Snake): if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] - if ctx.target: + if ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): From f28646c6e6de99af6889336c59be592b8079588f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:22:13 -0600 Subject: [PATCH 46/95] Fix invite link bypass in encode --- jarvis/cogs/dev.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f08bf22..c7b5e6d 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -211,6 +211,12 @@ class DevCog(Scale): required=True, ) async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: + if invites.search(data): + await ctx.send( + "Please don't use this to bypass invite restrictions", + ephemeral=True, + ) + return mstr = method method = getattr(base64, method + "encode") try: From 427f4a9cf99036e7b8f11fb72a24aca9aed8dcda Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:26:52 -0600 Subject: [PATCH 47/95] Fix remaining datetime.now calls --- jarvis/client.py | 2 +- jarvis/utils/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index bb90256..7cfef60 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -114,7 +114,7 @@ class Jarvis(Snake): guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC") - timestamp = int(datetime.now().timestamp()) + timestamp = int(datetime.now(tz=timezone.utc).timestamp()) timestamp = f"" arg_str = "" if ctx.target_id: diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 6b0d89a..3c5de79 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. Utility Functions.""" -from datetime import datetime +from datetime import datetime, timezone from pkgutil import iter_modules import git @@ -23,7 +23,7 @@ def build_embed( ) -> Embed: """Embed builder utility function.""" if not timestamp: - timestamp = datetime.now() + timestamp = datetime.now(tz=timezone.utc) embed = Embed( title=title, description=description, From 5bb592183e7639d2529b2d49abc4d2f88c349b01 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:59:18 -0600 Subject: [PATCH 48/95] Add `notify` setting, fix settings management --- jarvis/cogs/settings.py | 70 +++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 88cb3c5..4eebd93 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -124,79 +124,75 @@ class SettingsCog(Scale): await self.update_settings("noinvite", active, ctx.guild.id) await ctx.send(f"Settings applied. Automatic invite active: {active}") + @set_.subcommand(sub_cmd_name="notify", sub_cmd_description="Notify users of admin action?") + @slash_option(name="active", description="Notify?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_notify(self, ctx: InteractionContext, active: bool) -> None: + await ctx.defer() + await self.update_settings("notify", active, ctx.guild.id) + await ctx.send(f"Settings applied. Notifications active: {active}") + # Unset @unset.subcommand( sub_cmd_name="modlog", - sub_cmd_description="Set Moglod channel", - ) - @slash_option( - name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True + sub_cmd_description="Unset Modlog channel", ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: + async def _unset_modlog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("modlog", channel.id, ctx.guild.id) - await ctx.send(f"Setting `{channel.mention}` unset") + await self.delete_settings("modlog") + await ctx.send("Setting `modlog` unset") @unset.subcommand( sub_cmd_name="activitylog", - sub_cmd_description="Set Activitylog channel", - ) - @slash_option( - name="channel", - description="Activitylog Channel", - opt_type=OptionTypes.CHANNEL, - required=True, + sub_cmd_description="Unset Activitylog channel", ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: + async def _unset_activitylog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("activitylog", channel.id, ctx.guild.id) - await ctx.send(f"Setting `{channel.mention}` unset") + await self.delete_settings("activitylog") + await ctx.send("Setting `activitylog` unset") - @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") - @slash_option( - name="amount", - description="Amount of mentions (0 to disable)", - opt_type=OptionTypes.INTEGER, - required=True, - ) + @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Unset massmention output") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_massmention(self, ctx: InteractionContext, amount: int) -> None: + async def _unset_massmention(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("massmention") - await ctx.send(f"Setting `{amount}` unset") + await ctx.send("Setting `massmention` unset") - @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Unset verified role") @slash_option( name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_verified(self, ctx: InteractionContext, role: Role) -> None: + async def _unset_verified(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("verified") - await ctx.send(f"Setting `{role.name} unset`") + await ctx.send("Setting `massmention` unset") - @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") - @slash_option( - name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True - ) + @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Unset unverified role") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_unverified(self, ctx: InteractionContext, role: Role) -> None: + async def _unset_unverified(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("unverified") - await ctx.send(f"Setting `{role.name}` unset") + await ctx.send("Setting `unverified` unset") @unset.subcommand( - sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" + sub_cmd_name="noinvite", sub_cmd_description="Unset if invite deletion should happen" ) - @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() await self.delete_settings("noinvite") await ctx.send(f"Setting `{active}` unset") + @unset.subcommand(sub_cmd_name="notify", sub_cmd_description="Unset admin action notifications") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_notify(self, ctx: InteractionContext) -> None: + await ctx.defer() + await self.delete_settings("noinvite") + await ctx.send("Setting `notify` unset") + @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _view(self, ctx: InteractionContext) -> None: From ee86320c1604e08786cf4e17c3b5536dd225e957 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 11:35:08 -0600 Subject: [PATCH 49/95] Fix ModcaseCog logging --- jarvis/utils/cogs.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index a235c3c..0a30954 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,12 +1,24 @@ """Cog wrapper for command caching.""" from datetime import datetime, timedelta, timezone -from dis_snek import Context, Scale, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import find +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q -from jarvis_core.db.models import Action, Ban, Kick, Modlog, Mute, Note, Warning +from jarvis_core.db.models import ( + Action, + Ban, + Kick, + Modlog, + Mute, + Note, + Setting, + Warning, +) + +from jarvis.utils import build_embed MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} @@ -19,7 +31,7 @@ class CacheCog(Scale): self.cache = {} self._expire_interaction.start() - def check_cache(self, ctx: Context, **kwargs: dict) -> dict: + def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: """Check the cache.""" if not kwargs: kwargs = {} @@ -46,7 +58,7 @@ class ModcaseCog(Scale): self.bot = bot self.add_scale_postrun(self.log) - async def log(self, ctx: Context, *args: list, **kwargs: dict) -> None: + async def log(self, ctx: InteractionContext, *args: list, **kwargs: dict) -> None: """ Log a moderation activity in a moderation case. @@ -57,19 +69,35 @@ class ModcaseCog(Scale): if name not in ["Lock", "Lockdown", "Purge", "Roleping"]: user = kwargs.pop("user", None) - if not user: - # Log warning about missing user + if not user and not ctx.target_id: + self.logger.warn(f"Admin action {name} missing user, exiting") return + elif ctx.target_id: + user = ctx.target coll = MODLOG_LOOKUP.get(name, None) if not coll: - # Log warning about unsupported action + self.logger.warn(f"Unsupported action {name}, exiting") return action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) if not action: - # Log warning about missing action + self.logger.warn(f"Missing action {name}, exiting") return action = Action(action_type=name.lower(), parent=action.id) note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() + notify = await Setting.find_one(q(guild=ctx.guild.id, setting="notify", value=True)) + if notify and name not in ["Kick", "Ban"]: # Ignore Kick and Ban, as these are unique + fields = [ + EmbedField(name="Action Type", value=name, inline=False), + EmbedField( + name="Reason", value=kwargs.get("reason", None) or "N/A", inline=False + ), + ] + embed = build_embed( + title="Admin action taken", + description=f"Admin action has been taken against you in {ctx.guild.name}", + fields=fields, + ) + await user.send(embed=embed) From 27b286dab51fab8710b954f2ae5cf4d975b7a9ea Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:25:09 -0600 Subject: [PATCH 50/95] Fix saving settings --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 4eebd93..79c2b47 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -33,7 +33,7 @@ class SettingsCog(Scale): if not existing: existing = Setting(setting=setting, guild=guild, value=value) existing.value = value - updated = existing.save() + updated = await existing.commit() return updated is not None From 58c093bfa198bca9ca1d2183efe2d134c8a43580 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:25:24 -0600 Subject: [PATCH 51/95] Delete unused commands --- jarvis/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 34f5af8..2849f32 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -27,7 +27,9 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None -jarvis = Jarvis(intents=intents, sync_interactions=jconfig.sync) +jarvis = Jarvis( + intents=intents, sync_interactions=jconfig.sync, delete_unused_application_cmds=True +) async def run() -> None: From 0de6999ba6787701fc61403794c40c564485f5b8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:36:48 -0600 Subject: [PATCH 52/95] Update notify DM to look better --- jarvis/utils/cogs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 0a30954..7994290 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -100,4 +100,7 @@ class ModcaseCog(Scale): description=f"Admin action has been taken against you in {ctx.guild.name}", fields=fields, ) + guild_url = f"https://discord.com/channels/{ctx.guild.id}" + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url) + embed.set_thumbnail(url=ctx.guild.icon.url) await user.send(embed=embed) From 502c5c2ad199ebffc6f3421753df31124f7bd1bc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:40:42 -0600 Subject: [PATCH 53/95] Add extra info to util command --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 4cda0c9..efa0c03 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -50,7 +50,7 @@ class UtilCog(Scale): @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _status(self, ctx: InteractionContext) -> None: title = "J.A.R.V.I.S. Status" - desc = "All systems online" + desc = f"All systems online\nConnected to **{len(self.bot.guilds)}** guilds" color = "#3498db" fields = [] From de063c14114c204599dfa737835e278501d3a047 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 00:15:59 -0600 Subject: [PATCH 54/95] Add context menu for userinfo --- jarvis/cogs/util.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index efa0c03..0c284a2 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -17,8 +17,10 @@ from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.role import Role from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( + CommandTypes, OptionTypes, SlashCommandChoice, + context_menu, slash_command, slash_option, ) @@ -178,16 +180,6 @@ class UtilCog(Scale): await ctx.send(embed=embed, file=color_show) - @slash_command( - name="userinfo", - description="Get user info", - ) - @slash_option( - name="user", - description="User to get info of", - opt_type=OptionTypes.USER, - required=False, - ) async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: await ctx.defer() if not user: @@ -233,6 +225,23 @@ class UtilCog(Scale): await ctx.send(embed=embed) + @slash_command( + name="userinfo", + description="Get user info", + ) + @slash_option( + name="user", + description="User to get info of", + opt_type=OptionTypes.USER, + required=False, + ) + async def _userinfo_slsh(self, ctx: InteractionContext, user: User = None) -> None: + await self._userinfo() + + @context_menu(name="User Info", context_type=CommandTypes.USER) + async def _userinfo_menu(self, ctx: InteractionContext) -> None: + await self._userinfo(ctx, ctx.target) + @slash_command(name="serverinfo", description="Get server info") async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild From b5b7c8ca81ba342e56d5033245323ad50d6df387 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 00:17:52 -0600 Subject: [PATCH 55/95] Fix missing args in userinfo --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 0c284a2..dd07f19 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -236,7 +236,7 @@ class UtilCog(Scale): required=False, ) async def _userinfo_slsh(self, ctx: InteractionContext, user: User = None) -> None: - await self._userinfo() + await self._userinfo(ctx, user) @context_menu(name="User Info", context_type=CommandTypes.USER) async def _userinfo_menu(self, ctx: InteractionContext) -> None: From be0307fedc88ce7e2c139ef7b40fa1b76c627e85 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 09:22:47 -0600 Subject: [PATCH 56/95] Remove unnecessary dev dependencies --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 110df1f..3a00631 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,6 @@ aiohttp = "^3.8.1" pastypy = "^1.0.1" dateparser = "^1.1.1" -[tool.poetry.dev-dependencies] -python-lsp-server = {extras = ["all"], version = "^1.3.3"} -black = "^22.1.0" - [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 20a4c741931ee4f8a386313796feb5bddec37152 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 4 Apr 2022 19:47:42 -0600 Subject: [PATCH 57/95] Fix on_member_join --- jarvis/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/client.py b/jarvis/client.py index 7cfef60..817fc82 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -193,6 +193,7 @@ class Jarvis(Snake): await channel.send(embed=embed) # Events + @listen() async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild From b950bf12552b15d1c8217bd4158ae0c45b4c3098 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 12 Apr 2022 10:56:45 -0600 Subject: [PATCH 58/95] Fix bug in rolegiver preventing saving of roles --- jarvis/cogs/rolegiver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 878ece4..5b7c436 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -50,7 +50,7 @@ class RolegiverCog(Scale): return setting.roles.append(role.id) - setting.save() + await setting.commit() roles = [] for role_id in setting.roles: @@ -118,7 +118,7 @@ class RolegiverCog(Scale): if role: removed_roles.append(role) setting.roles.remove(int(to_delete)) - setting.save() + await setting.commit() for row in components: for component in row.components: @@ -369,7 +369,7 @@ class RolegiverCog(Scale): for role_id in setting.roles: if role_id not in guild_role_ids: setting.roles.remove(role_id) - setting.save() + await setting.commit() await ctx.send("Rolegiver cleanup finished") From 13ebc33ae1cb0980cdef00d6186dec8dacc57ec7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 12 Apr 2022 11:10:48 -0600 Subject: [PATCH 59/95] Add guild name to error handling --- jarvis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 817fc82..be20753 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -32,6 +32,7 @@ DEFAULT_SITE = "https://paste.zevs.me" ERROR_MSG = """ Command Information: + Guild: {guild_name} Name: {invoked_name} Args: {arg_str} @@ -129,6 +130,7 @@ class Jarvis(Snake): "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" ) full_message = ERROR_MSG.format( + guild_name=ctx.guild.name, error_time=error_time, invoked_name=ctx.invoked_name, arg_str=arg_str, From 48338fc22b6cff79637361071bc026b3971a61d8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 13 Apr 2022 13:05:21 -0600 Subject: [PATCH 60/95] Fix unsetting settings --- jarvis/cogs/settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 79c2b47..722ed6c 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -140,7 +140,7 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_modlog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("modlog") + await self.delete_settings("modlog", ctx.guild.id) await ctx.send("Setting `modlog` unset") @unset.subcommand( @@ -150,14 +150,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_activitylog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("activitylog") + await self.delete_settings("activitylog", ctx.guild.id) await ctx.send("Setting `activitylog` unset") @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Unset massmention output") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_massmention(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("massmention") + await self.delete_settings("massmention", ctx.guild.id) await ctx.send("Setting `massmention` unset") @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Unset verified role") @@ -167,14 +167,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_verified(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("verified") + await self.delete_settings("verified", ctx.guild.id) await ctx.send("Setting `massmention` unset") @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Unset unverified role") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_unverified(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("unverified") + await self.delete_settings("unverified", ctx.guild.id) await ctx.send("Setting `unverified` unset") @unset.subcommand( @@ -183,14 +183,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() - await self.delete_settings("noinvite") + await self.delete_settings("noinvite", ctx.guild.id) await ctx.send(f"Setting `{active}` unset") @unset.subcommand(sub_cmd_name="notify", sub_cmd_description="Unset admin action notifications") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_notify(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("noinvite") + await self.delete_settings("notify", ctx.guild.id) await ctx.send("Setting `notify` unset") @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") From 835acb8be5c6b8ecdfb539963f7b66adf0ed2c17 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Thu, 14 Apr 2022 23:06:38 -0600 Subject: [PATCH 61/95] Fix issue with rolegiver and get vs fetch --- jarvis/cogs/rolegiver.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 5b7c436..07cdcfe 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -56,7 +56,7 @@ class RolegiverCog(Scale): for role_id in setting.roles: if role_id == role.id: continue - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -92,7 +92,7 @@ class RolegiverCog(Scale): options = [] for role in setting.roles: - role: Role = await ctx.guild.get_role(role) + role: Role = await ctx.guild.fetch_role(role) option = SelectOption(label=role.name, value=str(role.id)) options.append(option) @@ -114,7 +114,7 @@ class RolegiverCog(Scale): ) removed_roles = [] for to_delete in context.context.values: - role = await ctx.guild.get_role(to_delete) + role = await ctx.guild.fetch_role(to_delete) if role: removed_roles.append(role) setting.roles.remove(int(to_delete)) @@ -126,7 +126,7 @@ class RolegiverCog(Scale): roles = [] for role_id in setting.roles: - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -174,7 +174,7 @@ class RolegiverCog(Scale): roles = [] for role_id in setting.roles: - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -212,7 +212,7 @@ class RolegiverCog(Scale): options = [] for role in setting.roles: - role: Role = await ctx.guild.get_role(role) + role: Role = await ctx.guild.fetch_role(role) option = SelectOption(label=role.name, value=str(role.id)) options.append(option) @@ -235,7 +235,7 @@ class RolegiverCog(Scale): added_roles = [] for role in context.context.values: - role = await ctx.guild.get_role(int(role)) + role = await ctx.guild.fetch_role(int(role)) added_roles.append(role) await ctx.author.add_role(role, reason="Rolegiver") From 2d91771f590c98a070dcb4fdd1b1fead0897bc5e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:10:51 -0600 Subject: [PATCH 62/95] Silently drop reminder deletion errors, closes #131 --- jarvis/cogs/remindme.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 23b09cf..b4f3ff8 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -276,7 +276,10 @@ class RemindmeCog(Scale): inline=False, ) ) - await reminder.delete() + try: + await reminder.delete() + except Exception: + pass # Silently drop error for row in components: for component in row.components: From f1061786733025cf40ad7ffd43f8d66679a080fb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:15:08 -0600 Subject: [PATCH 63/95] Make reminder fetch error ephemeral, closes #132 --- jarvis/cogs/remindme.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index b4f3ff8..fb6044f 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -318,7 +318,7 @@ class RemindmeCog(Scale): async def _fetch(self, ctx: InteractionContext, id: str) -> None: reminder = await Reminder.find_one(q(id=id)) if not reminder: - await ctx.send(f"Reminder {id} does not exist") + await ctx.send(f"Reminder `{id}` does not exist", hidden=True) return embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) @@ -329,6 +329,11 @@ class RemindmeCog(Scale): embed.set_thumbnail(url=ctx.author.display_avatar) await ctx.send(embed=embed, ephemeral=reminder.private) + if reminder.remind_at <= datetime.now(tz=timezone.utc): + try: + await reminder.delete() + except Exception: + pass # Silently drop error def setup(bot: Snake) -> None: From 90dfb77d1172f1446c77a220aa7867a6d59f4c1d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:38:04 -0600 Subject: [PATCH 64/95] Disable send command tracebacks --- jarvis/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 2849f32..fc18c43 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -28,7 +28,10 @@ intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.G restart_ctx = None jarvis = Jarvis( - intents=intents, sync_interactions=jconfig.sync, delete_unused_application_cmds=True + intents=intents, + sync_interactions=jconfig.sync, + delete_unused_application_cmds=True, + send_command_tracebacks=False, ) From 395e7f74fb77d390f94e9de27b3e38b13effc33a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:50:43 -0600 Subject: [PATCH 65/95] Fix settings typo --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 722ed6c..da89c9a 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -221,7 +221,7 @@ class SettingsCog(Scale): value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(EmbedField(name=setting.setting, value=value or "N/A", inlilne=False)) + fields.append(EmbedField(name=setting.setting, value=value or "N/A", inline=False)) embed = build_embed(title="Current Settings", description="", fields=fields) From e0924b085f6c1298db798bf1f7c028dd3bea9224 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:12:10 -0600 Subject: [PATCH 66/95] Fix settings view --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index da89c9a..9ef4b5c 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -221,7 +221,7 @@ class SettingsCog(Scale): value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(EmbedField(name=setting.setting, value=value or "N/A", inline=False)) + fields.append(EmbedField(name=setting.setting, value=str(value) or "N/A", inline=False)) embed = build_embed(title="Current Settings", description="", fields=fields) From 3f9c344cc7fb40fb4c201dfeb5ea279061fa805c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:28:36 -0600 Subject: [PATCH 67/95] Fix error in fetching rolegiver info --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 9ef4b5c..02d8ed0 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -216,7 +216,7 @@ class SettingsCog(Scale): elif setting.setting == "rolegiver": value = "" for _role in setting.value: - nvalue = await ctx.guild.fetch_role(value) + nvalue = await ctx.guild.fetch_role(_role) if value: value += "\n" + nvalue.mention else: From d79fc0e99402daf11e45ebf5340f01d86612089b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:31:52 -0600 Subject: [PATCH 68/95] Fix not getting existing role in settings view --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 02d8ed0..0ac0af5 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -217,7 +217,7 @@ class SettingsCog(Scale): value = "" for _role in setting.value: nvalue = await ctx.guild.fetch_role(_role) - if value: + if nvalue: value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" From a220b29303f4d964c85f3d1f0d3bf98734a282f1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:34:35 -0600 Subject: [PATCH 69/95] Update member events, fix error if activitylog channel no longer exists --- jarvis/client.py | 50 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index be20753..2458802 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -6,14 +6,19 @@ from datetime import datetime, timezone from aiohttp import ClientSession from dis_snek import Snake, listen -from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.api.events.discord import ( + MemberAdd, + MemberRemove, + MessageCreate, + MessageDelete, + MessageUpdate, +) from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message -from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger @@ -162,7 +167,7 @@ class Jarvis(Snake): async def on_command(self, ctx: InteractionContext) -> None: """Lepton on_command override.""" if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: - modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] @@ -192,13 +197,21 @@ class Jarvis(Snake): embed.set_footer( text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}" ) - await channel.send(embed=embed) + if channel: + await channel.send(embed=embed) + else: + self.logger.warning( + f"Activitylog channel no longer exists in {ctx.guild.name}, removing" + ) + await modlog.delete() # Events + # Member @listen() - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild + async def on_member_add(self, event: MemberAdd) -> None: + """Handle on_member_add event.""" + user = event.member + guild = event.guild unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}") @@ -206,6 +219,25 @@ class Jarvis(Snake): if role not in user.roles: await user.add_role(role, reason="User just joined and is unverified") + @listen() + async def on_member_remove(self, event: MemberRemove) -> None: + """Handle on_member_remove event.""" + user = event.member + guild = event.guild + log = await Setting.find_one(q(guild=guild.id, setting="activitylog")) + if log: + self.logger.debug(f"User {user.id} left {guild.id}") + channel = await guild.fetch_channel(log.channel) + embed = build_embed( + title="Member Left", + desciption=f"{user.username}#{user.discriminator} left {guild.name}", + fields=[], + ) + embed.set_author(name=user.username, icon_url=user.avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + await channel.send(embed=embed) + + # Message async def autopurge(self, message: Message) -> None: """Handle autopurge events.""" autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) @@ -429,7 +461,7 @@ class Jarvis(Snake): before = event.before after = event.after if not after.author.bot: - modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=after.guild.id, setting="activitylog")) if modlog: if not before or before.content == after.content or before.content is None: return @@ -481,7 +513,7 @@ class Jarvis(Snake): async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" message = event.message - modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=message.guild.id, setting="activitylog")) if modlog: try: content = message.content or "N/A" From 2235479b4015424a3e9e135eb6bafc18515af8e5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:35:14 -0600 Subject: [PATCH 70/95] Fix issue with reminder not found --- jarvis/cogs/remindme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index fb6044f..54a51fc 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -318,7 +318,7 @@ class RemindmeCog(Scale): async def _fetch(self, ctx: InteractionContext, id: str) -> None: reminder = await Reminder.find_one(q(id=id)) if not reminder: - await ctx.send(f"Reminder `{id}` does not exist", hidden=True) + await ctx.send(f"Reminder `{id}` does not exist", ephemeral=True) return embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) From 785f67c9c4cd777b990e2d2fa02b1e22f8b8b91a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:41:21 -0600 Subject: [PATCH 71/95] No longer allow warning user not in guild --- jarvis/cogs/admin/warning.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index e8b9dbb..031ee86 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -5,7 +5,7 @@ from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import User +from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, slash_command, @@ -44,7 +44,7 @@ class WarningCog(ModcaseCog): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _warn( - self, ctx: InteractionContext, user: User, reason: str, duration: int = 24 + self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24 ) -> None: if len(reason) > 100: await ctx.send("Reason must be < 100 characters", ephemeral=True) @@ -55,6 +55,9 @@ class WarningCog(ModcaseCog): elif duration >= 120: await ctx.send("Duration must be < 5 days", ephemeral=True) return + if not await ctx.guild.fetch_member(user.id): + await ctx.send("User not in guild", ephemeral=True) + return await ctx.defer() await Warning( user=user.id, @@ -76,7 +79,7 @@ class WarningCog(ModcaseCog): required=False, ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: + async def _warnings(self, ctx: InteractionContext, user: Member, active: bool = True) -> None: warnings = ( await Warning.find( q( From a20449f7b24d40e8db185b723a7118ba4125ad71 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:47:38 -0600 Subject: [PATCH 72/95] Fix star add issues with deleted channels --- jarvis/cogs/starboard.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index b0bfccf..27681b1 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -2,7 +2,6 @@ import logging from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.message import Message @@ -130,8 +129,21 @@ class StarboardCog(Scale): return channel_list = [] + to_delete = [] for starboard in starboards: - channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels)) + channel = await ctx.guild.fetch_channel(starboard.channel) + if channel: + channel_list.append(channel) + else: + to_delete.append(starboard) + + for starboard in to_delete: + await starboard.delete() + + select_channels = [] + for idx, x in enumerate(channel_list): + if x: + select_channels.append(SelectOption(label=x.name, value=str(idx))) select_channels = [ SelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list) From e66b15b2d60903ec5f9d5c93216d87e5cc1a4852 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:50:12 -0600 Subject: [PATCH 73/95] Fix name conflicts in star add --- jarvis/cogs/starboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 27681b1..66f94a4 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -131,9 +131,9 @@ class StarboardCog(Scale): channel_list = [] to_delete = [] for starboard in starboards: - channel = await ctx.guild.fetch_channel(starboard.channel) - if channel: - channel_list.append(channel) + c = await ctx.guild.fetch_channel(starboard.channel) + if c: + channel_list.append(c) else: to_delete.append(starboard) From 6862c13fe4b561e49278a1d1cb4eb8862137e166 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:55:09 -0600 Subject: [PATCH 74/95] Remove invalid starboards --- jarvis/cogs/starboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 66f94a4..35516a0 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -132,9 +132,12 @@ class StarboardCog(Scale): to_delete = [] for starboard in starboards: c = await ctx.guild.fetch_channel(starboard.channel) - if c: + if c and isinstance(c, GuildText): channel_list.append(c) else: + self.logger.warn( + f"Starboard {starboard.channel} no longer valid in {ctx.guild.name}" + ) to_delete.append(starboard) for starboard in to_delete: From 410ee3c462f4a4fdc58ead84c70cbd375a028d1e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 14:06:43 -0600 Subject: [PATCH 75/95] Disallow default role from role fields --- jarvis/cogs/admin/roleping.py | 4 ++++ jarvis/cogs/rolegiver.py | 4 ++++ jarvis/cogs/settings.py | 6 ++++++ jarvis/cogs/util.py | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index e1cb6d7..e1ead45 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -43,6 +43,10 @@ class RolepingCog(Scale): await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return + if role.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` to roleping", ephemeral=True) + return + _ = await Roleping( role=role.id, guild=ctx.guild.id, diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 07cdcfe..e159828 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -37,6 +37,10 @@ class RolegiverCog(Scale): @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` to rolegiver", ephemeral=True) + return + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if setting and role.id in setting.roles: await ctx.send("Role already in rolegiver", ephemeral=True) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 0ac0af5..d609111 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -100,6 +100,9 @@ class SettingsCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_verified(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot set verified to `@everyone`", ephemeral=True) + return await ctx.defer() await self.update_settings("verified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New verified role is `{role.name}`") @@ -110,6 +113,9 @@ class SettingsCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_unverified(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot set unverified to `@everyone`", ephemeral=True) + return await ctx.defer() await self.update_settings("unverified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New unverified role is `{role.name}`") diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index dd07f19..0976a54 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -146,7 +146,7 @@ class UtilCog(Scale): async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None: fields = [ EmbedField(name="ID", value=str(role.id), inline=True), - EmbedField(name="Name", value=role.name, inline=True), + EmbedField(name="Name", value=role.mention, inline=True), EmbedField(name="Color", value=str(role.color.hex), inline=True), EmbedField(name="Mention", value=f"`{role.mention}`", inline=True), EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True), From a0bb1a6c989f1f76d270ee1b3eb842f3532db1fc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 14:51:36 -0600 Subject: [PATCH 76/95] Catch URL errors in resize --- jarvis/cogs/image.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index d15fdd8..bcd57db 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -94,9 +94,20 @@ class ImageCog(Scale): filename = url.split("/")[-1] data = None - async with self._session.get(url) as resp: - if resp.status == 200: - data = await resp.read() + try: + async with self._session.get(url) as resp: + resp.raise_for_status() + if resp.content_type in ["image/jpeg", "image/png"]: + data = await resp.read() + else: + await ctx.send( + "Unsupported content type. Please send a URL to a JPEG or PNG", + ephemeral=True, + ) + return + except Exception: + await ctx.send("Failed to retrieve image. Please verify url", ephemeral=True) + return size = len(data) if size <= tgt_size: From 05527a74a31db6022151a5b64df6627afec0bcaa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 15:33:42 -0600 Subject: [PATCH 77/95] Allow users with MANAGE_GUILD permissions to be exempt from roleping --- jarvis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 2458802..12b0380 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -339,6 +339,8 @@ class Jarvis(Snake): async def roleping(self, message: Message) -> None: """Handle roleping events.""" + if message.author.has_permission(Permissions.MANAGE_GUILD): + return if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: return rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) From f5cba3ce3a8044549182a1f6b8f195501b03b45a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 15:44:23 -0600 Subject: [PATCH 78/95] Add URL button to ctc2 about --- jarvis/cogs/ctc2.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index a007628..ee061e0 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -5,6 +5,7 @@ import re import aiohttp from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( @@ -45,7 +46,14 @@ class CTCCog(Scale): @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _about(self, ctx: InteractionContext) -> None: - await ctx.send("See https://completethecode.com for more information") + components = [ + ActionRow( + Button(style=ButtonStyles.URL, url="https://completethecode.com", label="More Info") + ) + ] + await ctx.send( + "See https://completethecode.com for more information", components=components + ) @ctc2.subcommand( sub_cmd_name="pw", From 6c1753d5d2258bf618bd20d343d20e104120f46a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:01:29 -0600 Subject: [PATCH 79/95] Silently ignore db deletion errors, closes #134 --- jarvis/cogs/admin/roleping.py | 8 +++++++- jarvis/cogs/remindme.py | 4 ++-- jarvis/cogs/starboard.py | 5 ++++- jarvis/cogs/twitter.py | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index e1ead45..7f62bf7 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -67,7 +67,10 @@ class RolepingCog(Scale): await ctx.send("Roleping does not exist", ephemeral=True) return - await roleping.delete() + try: + await roleping.delete() + except Exception: + self.logger.debug("Ignoring deletion error") await ctx.send(f"Role `{role.name}` removed from roleping.") @roleping.subcommand(sub_cmd_name="list", sub_cmd_description="Lick all blocklisted roles") @@ -190,6 +193,9 @@ class RolepingCog(Scale): async def _roleping_bypass_role( self, ctx: InteractionContext, bypass: Role, role: Role ) -> None: + if bypass.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` as a bypass", ephemeral=True) + return roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 54a51fc..9623d5a 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -279,7 +279,7 @@ class RemindmeCog(Scale): try: await reminder.delete() except Exception: - pass # Silently drop error + self.logger.debug("Ignoring deletion error") for row in components: for component in row.components: @@ -333,7 +333,7 @@ class RemindmeCog(Scale): try: await reminder.delete() except Exception: - pass # Silently drop error + self.logger.debug("Ignoring deletion error") def setup(bot: Snake) -> None: diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 35516a0..e285b4e 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -141,7 +141,10 @@ class StarboardCog(Scale): to_delete.append(starboard) for starboard in to_delete: - await starboard.delete() + try: + await starboard.delete() + except Exception: + self.logger.debug("Ignoring deletion error") select_channels = [] for idx, x in enumerate(channel_list): diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index d3e7af4..6bc5416 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -150,7 +150,10 @@ class TwitterCog(Scale): ) for to_delete in context.context.values: follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete)) - await follow.delete() + try: + await follow.delete() + except Exception: + self.logger.debug("Ignoring deletion error") for row in components: for component in row.components: component.disabled = True From c4e822d7321ca44e1d5371640fd733c77c27bf35 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:14:01 -0600 Subject: [PATCH 80/95] Add confirmation to settings clear, closes #82 --- jarvis/cogs/settings.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index d609111..e293da8 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,9 +1,11 @@ """J.A.R.V.I.S. Settings Management Cog.""" +import asyncio import logging from typing import Any from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.role import Role @@ -236,9 +238,33 @@ class SettingsCog(Scale): @settings.subcommand(sub_cmd_name="clear", sub_cmd_description="Clear all settings") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _clear(self, ctx: InteractionContext) -> None: - async for setting in Setting.find(q(guild=ctx.guild.id)): - await setting.delete() - await ctx.send("Guild settings cleared") + components = [ + ActionRow( + Button(style=ButtonStyles.RED, emoji="✖️", custom_id="no"), + Button(style=ButtonStyles.GREEN, emoji="✔️", custom_id="yes"), + ) + ] + message = await ctx.send("***Are you sure?***", components=components) + try: + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, + timeout=60 * 5, + ) + if context.context.custom_id == "yes": + async for setting in Setting.find(q(guild=ctx.guild.id)): + await setting.delete() + await ctx.send("Guild settings cleared") + else: + for row in components: + for component in row.components: + component.disabled = True + await message.edit(components=components) + except asyncio.TimeoutError: + for row in components: + for component in row.components: + component.disabled = True + await message.edit(components=components) def setup(bot: Snake) -> None: From d278ca8b203c9643a0eb2989b706e5058ed83e42 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:15:55 -0600 Subject: [PATCH 81/95] Fix response to settings clear --- jarvis/cogs/settings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index e293da8..9ef4c41 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -251,15 +251,15 @@ class SettingsCog(Scale): messages=message, timeout=60 * 5, ) + content = "***Are you sure?***" if context.context.custom_id == "yes": async for setting in Setting.find(q(guild=ctx.guild.id)): await setting.delete() - await ctx.send("Guild settings cleared") - else: - for row in components: - for component in row.components: - component.disabled = True - await message.edit(components=components) + content = "Guild settings cleared" + for row in components: + for component in row.components: + component.disabled = True + await context.context.edit_origin(content=content, components=components) except asyncio.TimeoutError: for row in components: for component in row.components: From 6d051dc47a9817775c3cd4f76b1557108b0823b6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:17:29 -0600 Subject: [PATCH 82/95] More clear message on settings clear no confirmation --- jarvis/cogs/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 9ef4c41..c471cc2 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -256,6 +256,8 @@ class SettingsCog(Scale): async for setting in Setting.find(q(guild=ctx.guild.id)): await setting.delete() content = "Guild settings cleared" + else: + content = "Guild settings not cleared" for row in components: for component in row.components: component.disabled = True @@ -264,7 +266,7 @@ class SettingsCog(Scale): for row in components: for component in row.components: component.disabled = True - await message.edit(components=components) + await message.edit(content="Guild settings not cleared", components=components) def setup(bot: Snake) -> None: From b5242aabbe5a1d7cf4b171091c8d06f78e6e4d32 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:54:10 -0600 Subject: [PATCH 83/95] Add basic message commands for getting log info --- jarvis/cogs/botutil.py | 46 +++ poetry.lock | 627 +++-------------------------------------- 2 files changed, 92 insertions(+), 581 deletions(-) create mode 100644 jarvis/cogs/botutil.py diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py new file mode 100644 index 0000000..e897742 --- /dev/null +++ b/jarvis/cogs/botutil.py @@ -0,0 +1,46 @@ +"""JARVIS bot utility commands.""" +import logging +from io import BytesIO + +from aiofile import AIOFile, LineReader +from dis_snek import MessageContext, Scale, Snake +from dis_snek.models.discord.file import File +from dis_snek.models.snek.command import message_command + + +class BotutilCog(Scale): + """JARVIS Bot Utility Cog.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + + @message_command(name="tail") + async def _tail(self, ctx: MessageContext, count: int = 10) -> None: + if ctx.author.id != self.bot.owner.id: + return + lines = [] + async with AIOFile("jarvis.log", "r") as af: + async for line in LineReader(af): + lines.append(line) + if len(lines) == count: + lines.pop(0) + log = "\n".join(lines) + await ctx.reply(content=f"```\n{log}\n```") + + @message_command(name="log") + async def _log(self, ctx: MessageContext) -> None: + if ctx.author.id != self.bot.owner.id: + return + + async with AIOFile("jarvis.log", "r") as af: + with BytesIO() as file_bytes: + async for chunk in af.read(8192): + file_bytes.write(chunk) + log = File(file_bytes, file_name="jarvis.log") + await ctx.reply(content="Here's the latest log", file=log) + + +def setup(bot: Snake) -> None: + """Add BotutilCog to J.A.R.V.I.S.""" + BotutilCog(bot) diff --git a/poetry.lock b/poetry.lock index a3067a7..74b3af0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,17 @@ +[[package]] +name = "aiofile" +version = "3.7.4" +description = "Asynchronous file operations." +category = "main" +optional = false +python-versions = ">3.4.*, <4" + +[package.dependencies] +caio = ">=0.9.0,<0.10.0" + +[package.extras] +develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] + [[package]] name = "aiohttp" version = "3.8.1" @@ -29,18 +43,6 @@ python-versions = ">=3.6" [package.dependencies] frozenlist = ">=1.1.0" -[[package]] -name = "astroid" -version = "2.9.3" -description = "An abstract syntax tree for Python with inference support." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -wrapt = ">=1.11,<1.14" - [[package]] name = "async-timeout" version = "4.0.2" @@ -64,37 +66,15 @@ tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)" tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] -name = "autopep8" -version = "1.6.0" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -category = "dev" +name = "caio" +version = "0.9.5" +description = "Asynchronous file IO for Linux Posix and Windows." +category = "main" optional = false -python-versions = "*" - -[package.dependencies] -pycodestyle = ">=2.8.0" -toml = "*" - -[[package]] -name = "black" -version = "22.1.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = ">=1.1.0" +python-versions = ">=3.5.*, <4" [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +develop = ["aiomisc", "pytest", "pytest-cov"] [[package]] name = "certifi" @@ -115,25 +95,6 @@ python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] -[[package]] -name = "click" -version = "8.0.4" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "dateparser" version = "1.1.1" @@ -178,19 +139,6 @@ python-versions = ">=3.7" [package.dependencies] typing_extensions = ">=4,<5" -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" - [[package]] name = "frozenlist" version = "1.3.0" @@ -229,20 +177,6 @@ category = "main" optional = false python-versions = ">=3.5" -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] -plugins = ["setuptools"] - [[package]] name = "jarvis-core" version = "0.7.0" @@ -256,6 +190,7 @@ develop = false dis-snek = "*" motor = "^2.5.1" orjson = "^3.6.6" +pytz = "^2022.1" PyYAML = "^6.0" umongo = "^3.1.0" @@ -263,30 +198,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "b81ea66d12b1a32c8291cbe9e14fe17b8e020d08" - -[[package]] -name = "jedi" -version = "0.18.1" -description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -parso = ">=0.8.0,<0.9.0" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.7.1" -description = "A fast and thorough lazy object proxy." -category = "dev" -optional = false -python-versions = ">=3.6" +resolved_reference = "15521e73fb84fb4282a484ff0c9bb88ed1d144ae" [[package]] name = "marshmallow" @@ -305,14 +217,6 @@ docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", " lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "mongoengine" version = "0.23.1" @@ -346,14 +250,6 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "numpy" version = "1.22.3" @@ -410,18 +306,6 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - [[package]] name = "pastypy" version = "1.0.1" @@ -434,25 +318,17 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} urllib3 = {version = "1.26.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version < \"4\""} yarl = {version = "1.7.2", markers = "python_version >= \"3.6\""} -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - [[package]] name = "pillow" version = "9.0.1" @@ -461,30 +337,6 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "platformdirs" -version = "2.5.1" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - [[package]] name = "psutil" version = "5.9.0" @@ -496,14 +348,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "pycryptodome" version = "3.14.1" @@ -512,44 +356,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "pydocstyle" -version = "6.1.1" -description = "Python docstring style checker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -snowballstemmer = "*" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pylint" -version = "2.12.2" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -astroid = ">=2.9.0,<2.10" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" -platformdirs = ">=2.2.0" -toml = ">=0.9.2" - [[package]] name = "pymongo" version = "3.12.3" @@ -606,56 +412,6 @@ requests-toolbelt = ">=0.9.1" autocompletion = ["argcomplete (>=1.10.0,<3)"] yaml = ["PyYaml (>=5.2)"] -[[package]] -name = "python-lsp-jsonrpc" -version = "1.0.0" -description = "JSON RPC 2.0 server library" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -ujson = ">=3.0.0" - -[package.extras] -test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] - -[[package]] -name = "python-lsp-server" -version = "1.4.1" -description = "Python Language Server for the Language Server Protocol" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -autopep8 = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"all\""} -flake8 = {version = ">=4.0.0,<4.1.0", optional = true, markers = "extra == \"all\""} -jedi = ">=0.17.2,<0.19.0" -mccabe = {version = ">=0.6.0,<0.7.0", optional = true, markers = "extra == \"all\""} -pluggy = ">=1.0.0" -pycodestyle = {version = ">=2.8.0,<2.9.0", optional = true, markers = "extra == \"all\""} -pydocstyle = {version = ">=2.0.0", optional = true, markers = "extra == \"all\""} -pyflakes = {version = ">=2.4.0,<2.5.0", optional = true, markers = "extra == \"all\""} -pylint = {version = ">=2.5.0", optional = true, markers = "extra == \"all\""} -python-lsp-jsonrpc = ">=1.0.0" -rope = {version = ">=0.10.5", optional = true, markers = "extra == \"all\""} -ujson = ">=3.0.0" -yapf = {version = "*", optional = true, markers = "extra == \"all\""} - -[package.extras] -all = ["autopep8 (>=1.6.0,<1.7.0)", "flake8 (>=4.0.0,<4.1.0)", "mccabe (>=0.6.0,<0.7.0)", "pycodestyle (>=2.8.0,<2.9.0)", "pydocstyle (>=2.0.0)", "pyflakes (>=2.4.0,<2.5.0)", "pylint (>=2.5.0)", "rope (>=0.10.5)", "yapf"] -autopep8 = ["autopep8 (>=1.6.0,<1.7.0)"] -flake8 = ["flake8 (>=4.0.0,<4.1.0)"] -mccabe = ["mccabe (>=0.6.0,<0.7.0)"] -pycodestyle = ["pycodestyle (>=2.8.0,<2.9.0)"] -pydocstyle = ["pydocstyle (>=2.0.0)"] -pyflakes = ["pyflakes (>=2.4.0,<2.5.0)"] -pylint = ["pylint (>=2.5.0)"] -rope = ["rope (>0.10.5)"] -test = ["pylint (>=2.5.0)", "pytest", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5", "flaky"] -yapf = ["yapf"] - [[package]] name = "pytz" version = "2022.1" @@ -735,17 +491,6 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" -[[package]] -name = "rope" -version = "0.23.0" -description = "a python refactoring library..." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -dev = ["build", "pytest", "pytest-timeout"] - [[package]] name = "six" version = "1.16.0" @@ -762,22 +507,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" version = "2.0.1" @@ -837,14 +566,6 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""} devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] -[[package]] -name = "ujson" -version = "5.1.0" -description = "Ultra fast JSON encoder and decoder for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - [[package]] name = "ulid-py" version = "1.1.0" @@ -883,22 +604,6 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "wrapt" -version = "1.13.3" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "yapf" -version = "0.32.0" -description = "A formatter for Python code." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "yarl" version = "1.7.2" @@ -914,9 +619,13 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ce783f7855bb480b335731403679dc8249644570965a4cff6091eb377c5472df" +content-hash = "ef0ec99d8c98190d25cda0b8f4420738e569fd73b605dbae2a366a392a9cca7d" [metadata.files] +aiofile = [ + {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, + {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, +] aiohttp = [ {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, @@ -995,10 +704,6 @@ aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] -astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, -] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1007,34 +712,22 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] -autopep8 = [ - {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, - {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, -] -black = [ - {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, - {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, - {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, - {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, - {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, - {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, - {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, - {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, - {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, - {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, - {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, - {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, - {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, - {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, - {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, - {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, - {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, - {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, - {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +caio = [ + {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, + {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, + {file = "caio-0.9.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:013aa374158c5074b3c65a0da6b9c6b20a987d85fb317dd077b045e84e2478e1"}, + {file = "caio-0.9.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d767faf537a9ea774e8408ba15a0f1dc734f06857c2d28bdf4258a63b5885f42"}, + {file = "caio-0.9.5-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:97d9a10522a8a25798229fc1113cfaba3832b1cd0c1a3648b009b9740ef5e054"}, + {file = "caio-0.9.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4fe9eff5cf7a2d6f3f418aeeccd11ce9a38329e07527b6f52da085edb44bc2fd"}, + {file = "caio-0.9.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8b6be369139edd678817dc0a313392d710f66fb521c275dce0a9067089b346b"}, + {file = "caio-0.9.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3ffc6259239e03962f9e14829e02795ca9d196eedf32fe61688ba6ed33da46c8"}, + {file = "caio-0.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7a19dfdec6736affb645da233a6007c2590678490d2a1e0f1fb82a696c0a1ddf"}, + {file = "caio-0.9.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15f70d27e1009d279e4f9ff86290aad00b0511ce82a1879c40745244f0a9ec92"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0427a58c1814a091bfbb84318d344fdb9a68f3d49adc74e5fdc7bc9478e1e4fe"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f48fa58e5f699b428f1fd85e394ecec05be4048fcaf1fdf1981b748cd1e03a6"}, + {file = "caio-0.9.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01061288391020f28e1ab8b0437420f7fe1e0ecc29b4107f7a8dcf7789f33b22"}, + {file = "caio-0.9.5-py3-none-any.whl", hash = "sha256:3c74d84dff2bec5f93685cf2f32eb22e4cc5663434a9be5f4a759247229b69b3"}, + {file = "caio-0.9.5.tar.gz", hash = "sha256:167d9342a807bae441b2e88f9ecb62da2f236b319939a8679f68f510a0194c40"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1044,14 +737,6 @@ charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] -click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] dateparser = [ {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, @@ -1064,10 +749,6 @@ discord-typings = [ {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, {file = "discord_typings-0.3.1-py3-none-any.whl", hash = "sha256:65890c467751daa025dcef15683c32160f07427baf83380cfdf11d84ceec980a"}, ] -flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] frozenlist = [ {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, @@ -1141,62 +822,11 @@ idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] jarvis-core = [] -jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, -] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, - {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, -] marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, @@ -1266,10 +896,6 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] numpy = [ {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, @@ -1343,18 +969,10 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -parso = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, -] pastypy = [ {file = "pastypy-1.0.1-py3-none-any.whl", hash = "sha256:63cc664568f86f6ddeb7e5687422bbf4b338d067ea887ed240223c8cbcf6fd2d"}, {file = "pastypy-1.0.1.tar.gz", hash = "sha256:0393d1635b5031170eae3efaf376b14c3a4af7737c778d7ba7d56f2bd25bf5b1"}, ] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] pillow = [ {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, @@ -1392,14 +1010,6 @@ pillow = [ {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] -platformdirs = [ - {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, - {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] psutil = [ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, @@ -1434,10 +1044,6 @@ psutil = [ {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, ] -pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] pycryptodome = [ {file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"}, {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"}, @@ -1470,18 +1076,6 @@ pycryptodome = [ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"}, {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"}, ] -pydocstyle = [ - {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, - {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, -] -pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] -pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, -] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -1603,14 +1197,6 @@ python-gitlab = [ {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, ] -python-lsp-jsonrpc = [ - {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"}, - {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, -] -python-lsp-server = [ - {file = "python-lsp-server-1.4.1.tar.gz", hash = "sha256:be7f83298af9f0951a93972cafc9db04fd7cf5c05f20812515275f0ba70e342f"}, - {file = "python_lsp_server-1.4.1-py3-none-any.whl", hash = "sha256:e6bd0cf9530d8e2ba3b9c0ae10b99a16c33808bc9a7266afecc3900017b35326"}, -] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, @@ -1742,10 +1328,6 @@ requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] -rope = [ - {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, - {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1754,14 +1336,6 @@ smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1782,58 +1356,6 @@ tzlocal = [ {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, ] -ujson = [ - {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, - {file = "ujson-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0cae4a9c141856f7ad1a79c17ff1aaebf7fd8faa2f2c2614c37d6f82ed261d96"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba63b789d83ca92237dbc72041a268d91559f981c01763a107105878bae442e"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4e8f71e2fd42dce245bace7e2aa97dabef13926750a351eadca89a1e0f1abd"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f73946c047a38640b1f5a2a459237b7bdc417ab028a76c796e4eea984b359b9"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afe91153c2046fa8210b92def513124e0ea5b87ad8fa4c14fef8197204b980f1"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b1ef400fc73ab0cb61b74a662ad4207917223aba6f933a9fea9b0fbe75de2361"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c8a884d60dd2eed2fc95a9474d57ead82adf254f54caffb3d9e8ed185c49aba"}, - {file = "ujson-5.1.0-cp310-cp310-win32.whl", hash = "sha256:173b90a2c2836ee42f708df88ecfe3efbc4d868df73c9fcea8cb8f6f3ab93892"}, - {file = "ujson-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c45ad95e82155372d9908774db46e0ef7880af28a734d0b14eaa4f505e64982"}, - {file = "ujson-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4155a7c29bf330329519027c815e15e381c1fff22f50d26f135584d482bbd95d"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa616d0d3c594785c6e9b7f42686bb1c86f9e64aa0f30a72c86d8eb315f54194"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a48efcb5d3695b295c26835ed81048da8cd40e76c4fde2940c807aa452b560c9"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838d35eb9006d36f9241e95958d9f4819bcf1ea2ec155daf92d5751c31bcc62b"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:05aa6c7297a22081f65497b6f586de6b7060ea47c3ecda80896f47200e9dbf04"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ce441ab7ad1db592e2db95b6c2a1eb882123532897340afac1342c28819e9833"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9937e819196b894ffd00801b24f1042dabda142f355313c3f20410993219bc4f"}, - {file = "ujson-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:06bed66ae62d517f67a61cf53c056800b35ef364270723168a1db62702e2d30c"}, - {file = "ujson-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:74e41a0222e6e8136e38f103d6cc228e4e20f1c35cc80224a42804fd67fb35c8"}, - {file = "ujson-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7bbb87f040e618bebe8c6257b3e4e8ae2f708dcbff3270c84718b3360a152799"}, - {file = "ujson-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:68e38122115a8097fbe1cfe52979a797eaff91c10c1bf4b27774e5f30e7f723a"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b09843123425337d2efee5c8ff6519e4dfc7b044db66c8bd560517fc1070a157"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dca10174a3bd482d969a2d12d0aec2fdd63fb974e255ec0147e36a516a2d68a"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202ae52f4a53f03c42ead6d046b1a146517e93bd757f517bdeef0a26228e0260"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7a4bed7bd7b288cf73ba47bda27fdd1d78ef6906831489e7f296aef9e786eccb"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d423956f8dfd98a075c9338b886414b6e3c2817dbf67935797466c998af39936"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:083c1078e4de3a39019e590c43865b17e07a763fee25b012e650bb4f42c89703"}, - {file = "ujson-5.1.0-cp38-cp38-win32.whl", hash = "sha256:31671ad99f0395eb881d698f2871dc64ff00fbd4380c5d9bfd8bff3d4c8f8d88"}, - {file = "ujson-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:994eaf4369e6bc24258f59fe8c6345037abcf24557571814e27879851c4353aa"}, - {file = "ujson-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00d6ea9702c2eaeaf1a826934eaba1b4c609c873379bf54e36ba7b7e128edf94"}, - {file = "ujson-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a53c4fe8e1c067e6c98b4526e982ed9486f08578ad8eb5f0e94f8cadf0c1d911"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:368f855779fded560724a6448838304621f498113a116d66bc5ed5ad5ad3ca92"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd97e45a0f450ba2c43cda18147e54b8e41e886c22e3506c62f7d61e9e53b0d"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caeadbf95ce277f1f8f4f71913bc20c01f49fc9228f238920f9ff6f7645d2a5f"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:681fed63c948f757466eeb3aea98873e2ab8b2b18e9020c96a97479a513e2018"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6fc4376266ae67f6d8f9e69386ab950eb84ba345c6fdbeb1884fa5b773c8c76b"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:585271d6ad545a2ccfc237582f70c160e627735c89d0ca2bde24afa321bc0750"}, - {file = "ujson-5.1.0-cp39-cp39-win32.whl", hash = "sha256:b631af423e6d5d35f9f37fbcc4fbdb6085abc1c441cf864c64b7fbb5b150faf7"}, - {file = "ujson-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:08265db5ccff8b521ff68aee13a417d68cca784d7e711d961b92fda6ccffcc4f"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2b1c372583eb4363b42e21222d3a18116a41973781d502d61e1b0daf4b8352f"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51142c9d40439f299594e399bef8892a16586ded54c88d3af926865ca221a177"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba8be1717b1867a85b2413a8585bad0e4507a22d6af2c244e1c74151f6d5cc0"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b26d9d6eb9a0979d37f28c715e717a409c9e03163e5cd8fa73aab806351ab5"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b2c7e4afde0d36926b091fa9613b18b65e911fcaa60024e8721f2dcfedc25329"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:110633a8dda6c8ca78090292231e15381f8b2423e998399d4bc5f135149c722b"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdac161127ef8e0889180a4c07475457c55fe0bbd644436d8f4c7ef07565d653"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:452990c2b18445a7379a45873527d2ec47789b9289c13a17a3c1cc76b9641126"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5304ad25d100d50b5bc8513ef110335df678f66c7ccf3d4728c0c3aa69e08e0c"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ce620a6563b21aa3fbb1658bc1bfddb484a6dad542de1efb5121eb7bb4f2b93a"}, - {file = "ujson-5.1.0.tar.gz", hash = "sha256:a88944d2f99db71a3ca0c63d81f37e55b660edde0b07216fb65a3e46403ef004"}, -] ulid-py = [ {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, {file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"}, @@ -1846,63 +1368,6 @@ urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] -wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, -] -yapf = [ - {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"}, - {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"}, -] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, From 03d08dd16c2d77062c18fbd04fdfab00f4474fb5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:56:58 -0600 Subject: [PATCH 84/95] Add support for message commands in client --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 12b0380..cd9ee8a 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -79,9 +79,9 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) - async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: + async def _prerun(self, ctx: Context, *args, **kwargs) -> None: name = ctx.invoked_name - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") From a038930fd54cce183f9681973a9541003e2bd24e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:58:42 -0600 Subject: [PATCH 85/95] Fix error in log command --- jarvis/cogs/botutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index e897742..65dcb9b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -35,8 +35,8 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: - async for chunk in af.read(8192): - file_bytes.write(chunk) + raw = await af.read() + file_bytes.write(raw) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From ce9dda8239d9a71fda2e48827da796d5bd1d4b96 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:58:57 -0600 Subject: [PATCH 86/95] Remove extra spaces in tail command --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 65dcb9b..312bd4b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -25,7 +25,7 @@ class BotutilCog(Scale): lines.append(line) if len(lines) == count: lines.pop(0) - log = "\n".join(lines) + log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") @message_command(name="log") From 8f0872b30403b0f17a4c59067aa09e0e331cf5ff Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:59:57 -0600 Subject: [PATCH 87/95] Read log bytes instead of log string --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 312bd4b..c0a08f6 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -35,7 +35,7 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: - raw = await af.read() + raw = await af.read_bytes() file_bytes.write(raw) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From a4b0ae2b79ee5f76c8885b2aba2f9cbde89b2528 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:01:36 -0600 Subject: [PATCH 88/95] Fix on_command to support message commands --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index cd9ee8a..2fb93d5 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -164,14 +164,14 @@ class Jarvis(Snake): return await super().on_command_error(ctx, error, *args, **kwargs) # Modlog - async def on_command(self, ctx: InteractionContext) -> None: + async def on_command(self, ctx: Context) -> None: """Lepton on_command override.""" if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): From 08ab641ff18dfdb8553a5cdfe141ef4447081d47 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:02:07 -0600 Subject: [PATCH 89/95] Fix BytesIO reading --- jarvis/cogs/botutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index c0a08f6..23e0739 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -37,6 +37,7 @@ class BotutilCog(Scale): with BytesIO() as file_bytes: raw = await af.read_bytes() file_bytes.write(raw) + file_bytes.seek(0) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From 94c1997ab06777ea1f2d541759bf9dbf426ad2fa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:07:26 -0600 Subject: [PATCH 90/95] Add molter dependency --- jarvis/cogs/botutil.py | 6 ++-- poetry.lock | 73 +++++++++++++----------------------------- pyproject.toml | 2 ++ 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 23e0739..647232d 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -5,7 +5,7 @@ from io import BytesIO from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake from dis_snek.models.discord.file import File -from dis_snek.models.snek.command import message_command +from molter import msg_command class BotutilCog(Scale): @@ -15,7 +15,7 @@ class BotutilCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @message_command(name="tail") + @msg_command(name="tail") async def _tail(self, ctx: MessageContext, count: int = 10) -> None: if ctx.author.id != self.bot.owner.id: return @@ -28,7 +28,7 @@ class BotutilCog(Scale): log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") - @message_command(name="log") + @msg_command(name="log") async def _log(self, ctx: MessageContext) -> None: if ctx.author.id != self.bot.owner.id: return diff --git a/poetry.lock b/poetry.lock index 74b3af0..c1b3a01 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,17 +1,3 @@ -[[package]] -name = "aiofile" -version = "3.7.4" -description = "Asynchronous file operations." -category = "main" -optional = false -python-versions = ">3.4.*, <4" - -[package.dependencies] -caio = ">=0.9.0,<0.10.0" - -[package.extras] -develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] - [[package]] name = "aiohttp" version = "3.8.1" @@ -65,17 +51,6 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] -[[package]] -name = "caio" -version = "0.9.5" -description = "Asynchronous file IO for Linux Posix and Windows." -category = "main" -optional = false -python-versions = ">=3.5.*, <4" - -[package.extras] -develop = ["aiomisc", "pytest", "pytest-cov"] - [[package]] name = "certifi" version = "2021.10.8" @@ -116,7 +91,7 @@ langdetect = ["langdetect"] [[package]] name = "dis-snek" -version = "7.0.0" +version = "8.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -128,6 +103,10 @@ attrs = "*" discord-typings = "*" tomli = "*" +[package.extras] +all = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] +voice = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] + [[package]] name = "discord-typings" version = "0.3.1" @@ -217,6 +196,17 @@ docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", " lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +name = "molter" +version = "0.11.0" +description = "Shedding a new skin on Dis-Snek's commands." +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +dis-snek = ">=8.0.0" + [[package]] name = "mongoengine" version = "0.23.1" @@ -619,13 +609,9 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ef0ec99d8c98190d25cda0b8f4420738e569fd73b605dbae2a366a392a9cca7d" +content-hash = "af521f2487cac903d3766516484dcfdf999d222abacb1a8233248489591f6a34" [metadata.files] -aiofile = [ - {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, - {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, -] aiohttp = [ {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, @@ -712,23 +698,6 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] -caio = [ - {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, - {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, - {file = "caio-0.9.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:013aa374158c5074b3c65a0da6b9c6b20a987d85fb317dd077b045e84e2478e1"}, - {file = "caio-0.9.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d767faf537a9ea774e8408ba15a0f1dc734f06857c2d28bdf4258a63b5885f42"}, - {file = "caio-0.9.5-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:97d9a10522a8a25798229fc1113cfaba3832b1cd0c1a3648b009b9740ef5e054"}, - {file = "caio-0.9.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4fe9eff5cf7a2d6f3f418aeeccd11ce9a38329e07527b6f52da085edb44bc2fd"}, - {file = "caio-0.9.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8b6be369139edd678817dc0a313392d710f66fb521c275dce0a9067089b346b"}, - {file = "caio-0.9.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3ffc6259239e03962f9e14829e02795ca9d196eedf32fe61688ba6ed33da46c8"}, - {file = "caio-0.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7a19dfdec6736affb645da233a6007c2590678490d2a1e0f1fb82a696c0a1ddf"}, - {file = "caio-0.9.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15f70d27e1009d279e4f9ff86290aad00b0511ce82a1879c40745244f0a9ec92"}, - {file = "caio-0.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0427a58c1814a091bfbb84318d344fdb9a68f3d49adc74e5fdc7bc9478e1e4fe"}, - {file = "caio-0.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f48fa58e5f699b428f1fd85e394ecec05be4048fcaf1fdf1981b748cd1e03a6"}, - {file = "caio-0.9.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01061288391020f28e1ab8b0437420f7fe1e0ecc29b4107f7a8dcf7789f33b22"}, - {file = "caio-0.9.5-py3-none-any.whl", hash = "sha256:3c74d84dff2bec5f93685cf2f32eb22e4cc5663434a9be5f4a759247229b69b3"}, - {file = "caio-0.9.5.tar.gz", hash = "sha256:167d9342a807bae441b2e88f9ecb62da2f236b319939a8679f68f510a0194c40"}, -] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, @@ -742,8 +711,8 @@ dateparser = [ {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, ] dis-snek = [ - {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, - {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, + {file = "dis-snek-8.0.0.tar.gz", hash = "sha256:c035a4f664f9a638b80089f2a9a3330a4254fc227ef2c83c96582df06f392281"}, + {file = "dis_snek-8.0.0-py3-none-any.whl", hash = "sha256:3a89c8f78c27407fb67d42dfaa51be6a507306306779e45cd47687bd846b3b23"}, ] discord-typings = [ {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, @@ -827,6 +796,10 @@ marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] +molter = [ + {file = "molter-0.11.0-py3-none-any.whl", hash = "sha256:4ae311e34fc93bfa37643f86c382b1f104753e451e9904995f0f34f5edda8daa"}, + {file = "molter-0.11.0.tar.gz", hash = "sha256:1e06e021a00986b9218e67bce062cb52eab5c86e8187b28e68f7dca8df853aaa"}, +] mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, diff --git a/pyproject.toml b/pyproject.toml index 3a00631..971dbb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,8 @@ jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-cor aiohttp = "^3.8.1" pastypy = "^1.0.1" dateparser = "^1.1.1" +aiofile = "^3.7.4" +molter = "^0.11.0" [build-system] requires = ["poetry-core>=1.0.0"] From 79a50e9059f1d46dcb338915436c5850d7225957 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:09:20 -0600 Subject: [PATCH 91/95] Fix tail command off-by-1 error --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 647232d..98c961a 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -23,7 +23,7 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: async for line in LineReader(af): lines.append(line) - if len(lines) == count: + if len(lines) == count + 1: lines.pop(0) log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") From 3c23802ca002bf2d0f9d3ce36841557c1b1e6fe0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:12:15 -0600 Subject: [PATCH 92/95] Fix error in tail with too large of log --- jarvis/client.py | 2 +- jarvis/cogs/botutil.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 2fb93d5..0f9e956 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -123,7 +123,7 @@ class Jarvis(Snake): timestamp = int(datetime.now(tz=timezone.utc).timestamp()) timestamp = f"" arg_str = "" - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 98c961a..04f9db0 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -26,7 +26,14 @@ class BotutilCog(Scale): if len(lines) == count + 1: lines.pop(0) log = "".join(lines) - await ctx.reply(content=f"```\n{log}\n```") + if len(log) > 1500: + with BytesIO() as file_bytes: + file_bytes.write(log.encode("UTF8")) + file_bytes.seek(0) + log = File(file_bytes, file_name=f"tail_{count}.log") + await ctx.reply(content=f"Here's the last {count} lines of the log", file=log) + else: + await ctx.reply(content=f"```\n{log}\n```") @msg_command(name="log") async def _log(self, ctx: MessageContext) -> None: From dc01bb9d53f2f84b8b03a1e2ae872c27985740bd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:17:48 -0600 Subject: [PATCH 93/95] Improve message command support in client --- jarvis/client.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 0f9e956..5a5b343 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -19,7 +19,7 @@ from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message -from dis_snek.models.snek.context import Context, InteractionContext +from dis_snek.models.snek.context import Context, InteractionContext, MessageContext from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q @@ -125,11 +125,17 @@ class Jarvis(Snake): arg_str = "" if isinstance(ctx, InteractionContext) and ctx.target_id: ctx.kwargs["context target"] = ctx.target - for k, v in ctx.kwargs.items(): - arg_str += f" {k}: " - if isinstance(v, str) and len(v) > 100: - v = v[97] + "..." - arg_str += f"{v}\n" + if isinstance(ctx, InteractionContext): + for k, v in ctx.kwargs.items(): + arg_str += f" {k}: " + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f"{v}\n" + elif isinstance(ctx, MessageContext): + for v in ctx.args.items(): + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f" - {v}" callback_args = "\n".join(f" - {i}" for i in args) if args else " None" callback_kwargs = ( "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" @@ -173,12 +179,18 @@ class Jarvis(Snake): args = [] if isinstance(ctx, InteractionContext) and ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") - for k, v in ctx.kwargs.items(): - if isinstance(v, str): - v = v.replace("`", "\\`") - if len(v) > 100: - v = v[:97] + "..." - args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + if isinstance(ctx, InteractionContext): + for k, v in ctx.kwargs.items(): + if isinstance(v, str): + v = v.replace("`", "\\`") + if len(v) > 100: + v = v[:97] + "..." + args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + elif isinstance(ctx, MessageContext): + for v in ctx.args.items(): + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + args.append(f"{VAL_FMT}{v}{RESET}") args = " ".join(args) fields = [ EmbedField( From f899174fa04c5ab90e87f7626f381f0fd1d8c6f3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:18:27 -0600 Subject: [PATCH 94/95] Fix running .items() on list --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 5a5b343..f054764 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -132,7 +132,7 @@ class Jarvis(Snake): v = v[97] + "..." arg_str += f"{v}\n" elif isinstance(ctx, MessageContext): - for v in ctx.args.items(): + for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." arg_str += f" - {v}" @@ -187,7 +187,7 @@ class Jarvis(Snake): v = v[:97] + "..." args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") elif isinstance(ctx, MessageContext): - for v in ctx.args.items(): + for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." args.append(f"{VAL_FMT}{v}{RESET}") From 820ff56255a8dfc72316bbd16a86ee197c85f0a0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:37:36 -0600 Subject: [PATCH 95/95] Rename J.A.R.V.I.S. -> JARVIS --- jarvis/__init__.py | 4 ++-- jarvis/cogs/admin/__init__.py | 4 ++-- jarvis/cogs/admin/ban.py | 4 ++-- jarvis/cogs/admin/kick.py | 4 ++-- jarvis/cogs/admin/lock.py | 4 ++-- jarvis/cogs/admin/lockdown.py | 4 ++-- jarvis/cogs/admin/mute.py | 4 ++-- jarvis/cogs/admin/purge.py | 4 ++-- jarvis/cogs/admin/roleping.py | 4 ++-- jarvis/cogs/admin/warning.py | 4 ++-- jarvis/cogs/autoreact.py | 6 +++--- jarvis/cogs/botutil.py | 2 +- jarvis/cogs/ctc2.py | 6 +++--- jarvis/cogs/dbrand.py | 6 +++--- jarvis/cogs/dev.py | 8 ++++---- jarvis/cogs/gl.py | 16 ++++++++-------- jarvis/cogs/image.py | 6 +++--- jarvis/cogs/remindme.py | 6 +++--- jarvis/cogs/rolegiver.py | 6 +++--- jarvis/cogs/settings.py | 6 +++--- jarvis/cogs/starboard.py | 6 +++--- jarvis/cogs/twitter.py | 6 +++--- jarvis/cogs/util.py | 10 +++++----- jarvis/cogs/verify.py | 6 +++--- jarvis/config.py | 4 ++-- jarvis/utils/__init__.py | 8 ++++---- jarvis/utils/permissions.py | 2 +- 27 files changed, 75 insertions(+), 75 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index fc18c43..2b0804c 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,4 +1,4 @@ -"""Main J.A.R.V.I.S. package.""" +"""Main JARVIS package.""" import logging from importlib.metadata import version as _v @@ -36,7 +36,7 @@ jarvis = Jarvis( async def run() -> None: - """Run J.A.R.V.I.S.""" + """Run JARVIS""" logger.info("Starting JARVIS") logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index 2022554..f0fb1af 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Admin Cogs.""" +"""JARVIS Admin Cogs.""" import logging from dis_snek import Snake @@ -7,7 +7,7 @@ from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, def setup(bot: Snake) -> None: - """Add admin cogs to J.A.R.V.I.S.""" + """Add admin cogs to JARVIS""" logger = logging.getLogger(__name__) msg = "Loaded jarvis.cogs.admin.{}" ban.BanCog(bot) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 242c624..223b37e 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. BanCog.""" +"""JARVIS BanCog.""" import logging import re @@ -24,7 +24,7 @@ from jarvis.utils.permissions import admin_or_permissions class BanCog(ModcaseCog): - """J.A.R.V.I.S. BanCog.""" + """JARVIS BanCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 4175987..618e61b 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. KickCog.""" +"""JARVIS KickCog.""" import logging from dis_snek import InteractionContext, Permissions, Snake @@ -18,7 +18,7 @@ from jarvis.utils.permissions import admin_or_permissions class KickCog(ModcaseCog): - """J.A.R.V.I.S. KickCog.""" + """JARVIS KickCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 7c8f4a2..fb18a8b 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. LockCog.""" +"""JARVIS LockCog.""" import logging from typing import Union @@ -19,7 +19,7 @@ from jarvis.utils.permissions import admin_or_permissions class LockCog(Scale): - """J.A.R.V.I.S. LockCog.""" + """JARVIS LockCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index 527bfa5..8ee7ac5 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. LockdownCog.""" +"""JARVIS LockdownCog.""" import logging from dis_snek import InteractionContext, Scale, Snake @@ -93,7 +93,7 @@ async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: class LockdownCog(Scale): - """J.A.R.V.I.S. LockdownCog.""" + """JARVIS LockdownCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 3ad3a56..a8df321 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. MuteCog.""" +"""JARVIS MuteCog.""" import asyncio import logging from datetime import datetime, timedelta, timezone @@ -26,7 +26,7 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): - """J.A.R.V.I.S. MuteCog.""" + """JARVIS MuteCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index b8d6f4a..4036eb9 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. PurgeCog.""" +"""JARVIS PurgeCog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -16,7 +16,7 @@ from jarvis.utils.permissions import admin_or_permissions class PurgeCog(Scale): - """J.A.R.V.I.S. PurgeCog.""" + """JARVIS PurgeCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 7f62bf7..6fda235 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. RolepingCog.""" +"""JARVIS RolepingCog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class RolepingCog(Scale): - """J.A.R.V.I.S. RolepingCog.""" + """JARVIS RolepingCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 031ee86..97e2f7b 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. WarningCog.""" +"""JARVIS WarningCog.""" import logging from dis_snek import InteractionContext, Permissions, Snake @@ -22,7 +22,7 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(ModcaseCog): - """J.A.R.V.I.S. WarningCog.""" + """JARVIS WarningCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 9d421f2..db1a41f 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Autoreact Cog.""" +"""JARVIS Autoreact Cog.""" import logging import re from typing import Optional, Tuple @@ -20,7 +20,7 @@ from jarvis.utils.permissions import admin_or_permissions class AutoReactCog(Scale): - """J.A.R.V.I.S. Autoreact Cog.""" + """JARVIS Autoreact Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -207,5 +207,5 @@ class AutoReactCog(Scale): def setup(bot: Snake) -> None: - """Add AutoReactCog to J.A.R.V.I.S.""" + """Add AutoReactCog to JARVIS""" AutoReactCog(bot) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 04f9db0..2341c0e 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -50,5 +50,5 @@ class BotutilCog(Scale): def setup(bot: Snake) -> None: - """Add BotutilCog to J.A.R.V.I.S.""" + """Add BotutilCog to JARVIS""" BotutilCog(bot) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index ee061e0..90519dc 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Complete the Code 2 Cog.""" +"""JARVIS Complete the Code 2 Cog.""" import logging import re @@ -30,7 +30,7 @@ invites = re.compile( class CTCCog(Scale): - """J.A.R.V.I.S. Complete the Code 2 Cog.""" + """JARVIS Complete the Code 2 Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -149,5 +149,5 @@ class CTCCog(Scale): def setup(bot: Snake) -> None: - """Add CTCCog to J.A.R.V.I.S.""" + """Add CTCCog to JARVIS""" CTCCog(bot) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 5cea582..8fe1c98 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. dbrand cog.""" +"""JARVIS dbrand cog.""" import logging import re @@ -22,7 +22,7 @@ guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862 class DbrandCog(Scale): """ - dbrand functions for J.A.R.V.I.S. + dbrand functions for JARVIS Mostly support functions. Credit @cpixl for the shipping API """ @@ -198,5 +198,5 @@ class DbrandCog(Scale): def setup(bot: Snake) -> None: - """Add dbrandcog to J.A.R.V.I.S.""" + """Add dbrandcog to JARVIS""" DbrandCog(bot) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index c7b5e6d..4d2b743 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Developer Cog.""" +"""JARVIS Developer Cog.""" import base64 import hashlib import logging @@ -46,7 +46,7 @@ MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): - """J.A.R.V.I.S. Developer Cog.""" + """JARVIS Developer Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -266,7 +266,7 @@ class DevCog(Scale): embed = build_embed(title="Decoded Data", description="", fields=fields) await ctx.send(embed=embed) - @slash_command(name="cloc", description="Get J.A.R.V.I.S. lines of code") + @slash_command(name="cloc", description="Get JARVIS lines of code") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _cloc(self, ctx: InteractionContext) -> None: output = subprocess.check_output( # noqa: S603, S607 @@ -276,5 +276,5 @@ class DevCog(Scale): def setup(bot: Snake) -> None: - """Add DevCog to J.A.R.V.I.S.""" + """Add DevCog to JARVIS""" DevCog(bot) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 80c6a31..7fa80b2 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. GitLab Cog.""" +"""JARVIS GitLab Cog.""" import asyncio import logging from datetime import datetime @@ -26,14 +26,14 @@ guild_ids = [862402786116763668] class GitlabCog(Scale): - """J.A.R.V.I.S. GitLab Cog.""" + """JARVIS GitLab Cog.""" def __init__(self, bot: Snake): self.bot = bot self.logger = logging.getLogger(__name__) config = JarvisConfig.from_yaml() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) - # J.A.R.V.I.S. GitLab ID is 29 + # JARVIS GitLab ID is 29 self.project = self._gitlab.projects.get(29) gl = SlashCommand(name="gl", description="Get GitLab info", scopes=guild_ids) @@ -149,7 +149,7 @@ class GitlabCog(Scale): url=milestone.web_url, ) embed.set_author( - name="J.A.R.V.I.S.", + name="JARVIS", url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) @@ -240,7 +240,7 @@ class GitlabCog(Scale): title = "" if t_state: title = f"{t_state} " - title += f"J.A.R.V.I.S. {name}s" + title += f"JARVIS {name}s" fields = [] for item in api_list: description = item.description or "No description" @@ -256,10 +256,10 @@ class GitlabCog(Scale): title=title, description="", fields=fields, - url=f"https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./{name.replace(' ', '_')}s", + url=f"https://git.zevaryx.com/stark-industries/JARVIS/{name.replace(' ', '_')}s", ) embed.set_author( - name="J.A.R.V.I.S.", + name="JARVIS", url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) @@ -464,6 +464,6 @@ class GitlabCog(Scale): def setup(bot: Snake) -> None: - """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" + """Add GitlabCog to JARVIS if Gitlab token exists.""" if JarvisConfig.from_yaml().gitlab_token: GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index bcd57db..0dac7f8 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. image processing cog.""" +"""JARVIS image processing cog.""" import logging import re from io import BytesIO @@ -22,7 +22,7 @@ MIN_ACCURACY = 0.80 class ImageCog(Scale): """ - Image processing functions for J.A.R.V.I.S. + Image processing functions for JARVIS May be categorized under util later """ @@ -152,5 +152,5 @@ class ImageCog(Scale): def setup(bot: Snake) -> None: - """Add ImageCog to J.A.R.V.I.S.""" + """Add ImageCog to JARVIS""" ImageCog(bot) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9623d5a..7225412 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Remind Me Cog.""" +"""JARVIS Remind Me Cog.""" import asyncio import logging import re @@ -34,7 +34,7 @@ invites = re.compile( class RemindmeCog(Scale): - """J.A.R.V.I.S. Remind Me Cog.""" + """JARVIS Remind Me Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -337,5 +337,5 @@ class RemindmeCog(Scale): def setup(bot: Snake) -> None: - """Add RemindmeCog to J.A.R.V.I.S.""" + """Add RemindmeCog to JARVIS""" RemindmeCog(bot) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index e159828..d934de8 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Role Giver Cog.""" +"""JARVIS Role Giver Cog.""" import asyncio import logging @@ -22,7 +22,7 @@ from jarvis.utils.permissions import admin_or_permissions class RolegiverCog(Scale): - """J.A.R.V.I.S. Role Giver Cog.""" + """JARVIS Role Giver Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -379,5 +379,5 @@ class RolegiverCog(Scale): def setup(bot: Snake) -> None: - """Add RolegiverCog to J.A.R.V.I.S.""" + """Add RolegiverCog to JARVIS""" RolegiverCog(bot) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index c471cc2..d5e5086 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Settings Management Cog.""" +"""JARVIS Settings Management Cog.""" import asyncio import logging from typing import Any @@ -23,7 +23,7 @@ from jarvis.utils.permissions import admin_or_permissions class SettingsCog(Scale): - """J.A.R.V.I.S. Settings Management Cog.""" + """JARVIS Settings Management Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -270,5 +270,5 @@ class SettingsCog(Scale): def setup(bot: Snake) -> None: - """Add SettingsCog to J.A.R.V.I.S.""" + """Add SettingsCog to JARVIS""" SettingsCog(bot) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index e285b4e..85383c7 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Starboard Cog.""" +"""JARVIS Starboard Cog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -29,7 +29,7 @@ supported_images = [ class StarboardCog(Scale): - """J.A.R.V.I.S. Starboard Cog.""" + """JARVIS Starboard Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -319,5 +319,5 @@ class StarboardCog(Scale): def setup(bot: Snake) -> None: - """Add StarboardCog to J.A.R.V.I.S.""" + """Add StarboardCog to JARVIS""" StarboardCog(bot) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 6bc5416..a043d16 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Twitter Cog.""" +"""JARVIS Twitter Cog.""" import asyncio import logging @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class TwitterCog(Scale): - """J.A.R.V.I.S. Twitter Cog.""" + """JARVIS Twitter Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -244,5 +244,5 @@ class TwitterCog(Scale): def setup(bot: Snake) -> None: - """Add TwitterCog to J.A.R.V.I.S.""" + """Add TwitterCog to JARVIS""" TwitterCog(bot) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 0976a54..ff5d7b7 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Utility Cog.""" +"""JARVIS Utility Cog.""" import logging import platform import re @@ -39,7 +39,7 @@ JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA") class UtilCog(Scale): """ - Utility functions for J.A.R.V.I.S. + Utility functions for JARVIS Mostly system utility functions, but may change over time """ @@ -48,10 +48,10 @@ class UtilCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") + @slash_command(name="status", description="Retrieve JARVIS status") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _status(self, ctx: InteractionContext) -> None: - title = "J.A.R.V.I.S. Status" + title = "JARVIS Status" desc = f"All systems online\nConnected to **{len(self.bot.guilds)}** guilds" color = "#3498db" fields = [] @@ -376,5 +376,5 @@ class UtilCog(Scale): def setup(bot: Snake) -> None: - """Add UtilCog to J.A.R.V.I.S.""" + """Add UtilCog to JARVIS""" UtilCog(bot) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index f3cbe29..602f691 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Verify Cog.""" +"""JARVIS Verify Cog.""" import asyncio import logging from random import randint @@ -31,7 +31,7 @@ def create_layout() -> list: class VerifyCog(Scale): - """J.A.R.V.I.S. Verify Cog.""" + """JARVIS Verify Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -95,5 +95,5 @@ class VerifyCog(Scale): def setup(bot: Snake) -> None: - """Add VerifyCog to J.A.R.V.I.S.""" + """Add VerifyCog to JARVIS""" VerifyCog(bot) diff --git a/jarvis/config.py b/jarvis/config.py index 3ab039e..2aec603 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,4 +1,4 @@ -"""Load the config for J.A.R.V.I.S.""" +"""Load the config for JARVIS""" import os from jarvis_core.config import Config as CConfig @@ -25,7 +25,7 @@ class JarvisConfig(CConfig): class Config(object): - """Config singleton object for J.A.R.V.I.S.""" + """Config singleton object for JARVIS""" def __new__(cls, *args: list, **kwargs: dict): """Get the singleton config, or creates a new one.""" diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 3c5de79..751bfe6 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Utility Functions.""" +"""JARVIS Utility Functions.""" from datetime import datetime, timezone from pkgutil import iter_modules @@ -65,14 +65,14 @@ def modlog_embed( def get_extensions(path: str = jarvis.cogs.__path__) -> list: - """Get J.A.R.V.I.S. cogs.""" + """Get JARVIS cogs.""" config = get_config() vals = config.cogs or [x.name for x in iter_modules(path)] return ["jarvis.cogs.{}".format(x) for x in vals] def update() -> int: - """J.A.R.V.I.S. update utility.""" + """JARVIS update utility.""" repo = git.Repo(".") dirty = repo.is_dirty() current_hash = repo.head.object.hexsha @@ -87,6 +87,6 @@ def update() -> int: def get_repo_hash() -> str: - """J.A.R.V.I.S. current branch hash.""" + """JARVIS current branch hash.""" repo = git.Repo(".") return repo.head.object.hexsha diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index a20b686..1eacbc7 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -5,7 +5,7 @@ from jarvis.config import get_config def user_is_bot_admin() -> bool: - """Check if a user is a J.A.R.V.I.S. admin.""" + """Check if a user is a JARVIS admin.""" async def predicate(ctx: InteractionContext) -> bool: """Command check predicate."""