From db5a60a88f58f47328001a155dd20c481cc93399 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 13:22:48 -0700 Subject: [PATCH 01/34] Optimize reminders, changed how reminder time is chosen, closes #122 --- jarvis/cogs/remindme.py | 128 ++++++++++++++++++++------------------- jarvis/db/models.py | 1 + jarvis/tasks/reminder.py | 9 ++- jarvis/utils/__init__.py | 6 +- 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 70bd916..9639485 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,14 +2,16 @@ import asyncio import re from datetime import datetime, timedelta -from typing import List, Optional +from typing import List from bson import ObjectId from dis_snek import InteractionContext, Snake +from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommandChoice, slash_command, slash_option, ) @@ -19,6 +21,7 @@ from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") +time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?") invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, @@ -39,35 +42,29 @@ class RemindmeCog(CacheCog): required=True, ) @slash_option( - name="weeks", - description="Number of weeks?", - opt_type=OptionTypes.INTEGER, + name="delay", + description="How long? (i.e. 1w 3d 7h 5m 20s)", + opt_type=OptionTypes.STRING, required=False, ) @slash_option( - name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False - ) - @slash_option( - name="hours", - description="Number of hours?", - opt_type=OptionTypes.INTEGER, - required=False, - ) - @slash_option( - name="minutes", - description="Number of minutes?", - opt_type=OptionTypes.INTEGER, + name="private", + description="Send as DM?", + opt_type=OptionTypes.STRING, required=False, + choices=[ + SlashCommandChoice(name="Yes", value="y"), + SlashCommandChoice(name="No", value="n"), + ], ) async def _remindme( self, ctx: InteractionContext, - message: Optional[str] = None, - weeks: Optional[int] = 0, - days: Optional[int] = 0, - hours: Optional[int] = 0, - minutes: Optional[int] = 0, + message: str, + delay: str, + private: str = "n", ) -> None: + private = private == "y" if len(message) > 100: await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True) return @@ -81,31 +78,22 @@ class RemindmeCog(CacheCog): await ctx.send("Hey, you should probably make this readable", ephemeral=True) return - if not any([weeks, days, hours, minutes]): + 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, flags=re.I): + 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 + ) + return + + if not any(value for value in delta.items()): await ctx.send("At least one time period is required", ephemeral=True) return - weeks = abs(weeks) - days = abs(days) - hours = abs(hours) - minutes = abs(minutes) - - if weeks and weeks > 4: - await ctx.send("Cannot be farther than 4 weeks out!", ephemeral=True) - return - - elif days and days > 6: - await ctx.send("Use weeks instead of 7+ days, please.", ephemeral=True) - return - - elif hours and hours > 23: - await ctx.send("Use days instead of 24+ hours, please.", ephemeral=True) - return - - elif minutes and minutes > 59: - await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True) - return - reminders = Reminder.objects(user=ctx.author.id, active=True).count() if reminders >= 5: await ctx.send( @@ -115,12 +103,7 @@ class RemindmeCog(CacheCog): ) return - remind_at = datetime.utcnow() + timedelta( - weeks=weeks, - days=days, - hours=hours, - minutes=minutes, - ) + remind_at = datetime.now() + timedelta(**delta) _ = Reminder( user=ctx.author_id, @@ -128,6 +111,7 @@ class RemindmeCog(CacheCog): guild=ctx.guild.id, message=message, remind_at=remind_at, + private=private, active=True, ).save() @@ -150,7 +134,7 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.display_avatar.url) - await ctx.send(embed=embed) + await ctx.send(embed=embed, ephemeral=private) async def get_reminders_embed( self, ctx: InteractionContext, reminders: List[Reminder] @@ -158,13 +142,22 @@ class RemindmeCog(CacheCog): """Build embed for paginator.""" fields = [] for reminder in reminders: - fields.append( - EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), - value=f"{reminder.message}\n\u200b", - inline=False, + if reminder.private and isinstance(ctx.channel, GuildChannel): + fields.embed( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value="Please DM me this command to view the content of this reminder", + inline=False, + ) + ) + else: + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=f"{reminder.message}\n\u200b", + inline=False, + ) ) - ) embed = build_embed( title=f"{len(reminders)} Active Reminder(s)", @@ -246,13 +239,22 @@ class RemindmeCog(CacheCog): fields = [] for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): - fields.append( - EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), - value=reminder.message, - inline=False, + if reminder.private and isinstance(ctx.channel, GuildChannel): + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value="Private reminder", + inline=False, + ) + ) + else: + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=reminder.message, + inline=False, + ) ) - ) embed = build_embed( title="Deleted Reminder(s)", description="", @@ -260,7 +262,7 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.username + "#" + ctx.author.discriminator, + name=ctx.author.display_name + "#" + ctx.author.discriminator, icon_url=ctx.author.display_avatar.url, ) embed.set_thumbnail(url=ctx.author.display_avatar.url) diff --git a/jarvis/db/models.py b/jarvis/db/models.py index a99cb45..5c9b145 100644 --- a/jarvis/db/models.py +++ b/jarvis/db/models.py @@ -155,6 +155,7 @@ class Reminder(Document): message = StringField(max_length=100, required=True) remind_at = DateTimeField(required=True) created_at = DateTimeField(default=datetime.utcnow) + private = BooleanField(default=False) meta = {"db_alias": "main"} diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index 4c65e8e..b897e8e 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -25,7 +25,7 @@ async def _remind() -> None: fields=[], ) embed.set_author( - name=user.name + "#" + user.discriminator, icon_url=user.display_avatar.url + name=user.username + "#" + user.discriminator, icon_url=user.avatar.url ) embed.set_thumbnail(url=user.display_avatar.url) try: @@ -33,8 +33,13 @@ async def _remind() -> None: except Exception: guild = jarvis.jarvis.fetch_guild(reminder.guild) channel = guild.get_channel(reminder.channel) if guild else None - if channel: + if channel and not reminder.private: await channel.send(f"{user.mention}", embed=embed) + else: + await channel.send( + f"{user.mention}, you had a private reminder set for now, " + "but I couldn't send it to you." + ) finally: reminder.delete() diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 11318e1..c5d8b19 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -125,11 +125,7 @@ def find_all(predicate: Callable, sequence: Iterable) -> List[Any]: A list of matches """ - matches = [] - for el in sequence: - if predicate(el): - matches.append(el) - return matches + return [el for el in sequence if predicate(el)] def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]: From 5917f252e1992769d092347a4d645f19469b2819 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 13:23:56 -0700 Subject: [PATCH 02/34] Formatting changes --- jarvis/cogs/admin/ban.py | 25 ++++++++++++------------- jarvis/cogs/admin/kick.py | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index d72dd08..4f3cdf4 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -256,10 +256,11 @@ class BanCog(CacheCog): if not discord_ban_info and not database_ban_info: await ctx.send(f"Unable to find user {orig_user}", ephemeral=True) - elif discord_ban_info: + elif discord_ban_info and not database_ban_info: await self.discord_apply_unban(ctx, discord_ban_info.user, reason) + else: - discord_ban_info = find(lambda x: x.user.id == database_ban_info["id"], bans) + discord_ban_info = find(lambda x: x.user.id == database_ban_info.id, bans) if discord_ban_info: await self.discord_apply_unban(ctx, discord_ban_info.user, reason) else: @@ -273,9 +274,7 @@ class BanCog(CacheCog): admin=ctx.author.id, reason=reason, ).save() - await ctx.send( - "Unable to find user in Discord, " + "but removed entry from database." - ) + 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" @@ -300,9 +299,9 @@ class BanCog(CacheCog): 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, type: int = 0, active: int = 1) -> None: + async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, type=type, active=active) + exists = self.check_cache(ctx, btype=btype, active=active) if exists: await ctx.defer(ephemeral=True) await ctx.send( @@ -314,8 +313,8 @@ class BanCog(CacheCog): search = {"guild": ctx.guild.id} if active: search["active"] = True - if type > 0: - search["type"] = types[type] + if btype > 0: + search["type"] = types[btype] bans = Ban.objects(**search).order_by("-created_at") db_bans = [] fields = [] @@ -355,9 +354,9 @@ class BanCog(CacheCog): pages = [] title = "Active " if active else "Inactive " - if type > 0: - title += types[type] - if type == 1: + if btype > 0: + title += types[btype] + if btype == 1: title += "a" title += "bans" if len(fields) == 0: @@ -381,7 +380,7 @@ class BanCog(CacheCog): "user": ctx.author.id, "timeout": datetime.utcnow() + timedelta(minutes=5), "command": ctx.subcommand_name, - "type": type, + "btype": btype, "active": active, "paginator": paginator, } diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 623ddbb..eb09b2a 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -33,6 +33,7 @@ class KickCog(Scale): if len(reason) > 100: await ctx.send("Reason must be < 100 characters", ephemeral=True) return + guild_name = ctx.guild.name embed = build_embed( title=f"You have been kicked from {guild_name}", @@ -64,10 +65,10 @@ class KickCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - await ctx.send(embed=embed) _ = Kick( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, ).save() + await ctx.send(embed=embed) From adf770d624705e0695d6369d60a6c575be9819a5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 23:50:21 -0700 Subject: [PATCH 03/34] Optimization review, ref #120 --- jarvis/cogs/admin/mute.py | 4 -- jarvis/cogs/admin/purge.py | 11 +++- jarvis/cogs/admin/roleping.py | 33 ++--------- jarvis/cogs/admin/warning.py | 21 +------ jarvis/cogs/gitlab.py | 62 ++------------------ jarvis/cogs/image.py | 6 +- jarvis/events/member.py | 7 +-- jarvis/events/message.py | 13 +---- jarvis/logo.py | 106 ---------------------------------- 9 files changed, 29 insertions(+), 234 deletions(-) delete mode 100644 jarvis/logo.py diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index f34507d..9733e6e 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -119,7 +119,3 @@ class MuteCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) - 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}") - await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index e86cb1a..78a2c46 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -31,11 +31,11 @@ class PurgeCog(Scale): await ctx.send("Amount must be >= 1", ephemeral=True) return await ctx.defer() - channel = ctx.channel + messages = [] - async for message in channel.history(limit=amount + 1): + async for message in ctx.channel.history(limit=amount + 1): messages.append(message) - await channel.delete_messages(messages) + await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") _ = Purge( channel=ctx.channel.id, guild=ctx.guild.id, @@ -71,16 +71,19 @@ class PurgeCog(Scale): elif delay > 300: await ctx.send("Delay must be < 5 minutes", ephemeral=True) return + autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first() if autopurge: await ctx.send("Autopurge already exists.", ephemeral=True) return + _ = Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, ).save() + await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") @slash_command( @@ -126,6 +129,8 @@ class PurgeCog(Scale): if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return + autopurge.delay = delay autopurge.save() + await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.") diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 6a01ede..4d5fb70 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,7 +1,5 @@ """J.A.R.V.I.S. RolepingCog.""" -from datetime import datetime, timedelta - -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -14,17 +12,13 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import check from jarvis.db.models import Roleping -from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog +from jarvis.utils import build_embed, find_all from jarvis.utils.permissions import admin_or_permissions -class RolepingCog(CacheCog): +class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command( name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" ) @@ -35,6 +29,7 @@ class RolepingCog(CacheCog): if roleping: await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return + _ = Roleping( role=role.id, guild=ctx.guild.id, @@ -60,14 +55,6 @@ class RolepingCog(CacheCog): @slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles") async def _roleping_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 rolepings = Roleping.objects(guild=ctx.guild.id) if not rolepings: @@ -77,8 +64,8 @@ class RolepingCog(CacheCog): embeds = [] for roleping in rolepings: role = await ctx.guild.get_role(roleping.role) - bypass_roles = list(filter(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles)) - bypass_roles = [r.mention or "||`[redacted]`||" for r in bypass_roles] + 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 = [ await ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"] @@ -118,14 +105,6 @@ class RolepingCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index edd0db5..7145cca 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,6 +1,4 @@ """J.A.R.V.I.S. WarningCog.""" -from datetime import datetime, timedelta - from dis_snek import InteractionContext, Permissions, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.user import User @@ -87,14 +85,7 @@ class WarningCog(CacheCog): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, user_id=user.id, active=active) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return + warnings = Warning.objects( user=user.id, guild=ctx.guild.id, @@ -171,14 +162,4 @@ class WarningCog(CacheCog): paginator = Paginator(bot=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, - "user_id": user.id, - "active": active, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 6c769b0..f23062b 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. GitLab Cog.""" -from datetime import datetime, timedelta +from datetime import datetime import gitlab -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 Embed, EmbedField from dis_snek.models.snek.application_commands import ( @@ -14,16 +14,15 @@ from dis_snek.models.snek.application_commands import ( from jarvis.config import get_config from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog guild_ids = [862402786116763668] -class GitlabCog(CacheCog): +class GitlabCog(Scale): """J.A.R.V.I.S. GitLab Cog.""" def __init__(self, bot: Snake): - super().__init__(bot) + self.bot = bot config = get_config() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) # J.A.R.V.I.S. GitLab ID is 29 @@ -236,10 +235,11 @@ class GitlabCog(CacheCog): title += f"J.A.R.V.I.S. {name}s" fields = [] for item in api_list: + description = item.description or "No description" fields.append( EmbedField( name=f"[#{item.iid}] {item.title}", - value=item.description + f"\n\n[View this {name}]({item.web_url})", + value=(description[:200] + f"...\n\n[View this {name}]({item.web_url})"), inline=False, ) ) @@ -275,14 +275,6 @@ class GitlabCog(CacheCog): ], ) async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None: - exists = self.check_cache(ctx, state=state) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return await ctx.defer() m_state = state if m_state == "all": @@ -319,15 +311,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "state": state, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( @@ -348,14 +331,6 @@ class GitlabCog(CacheCog): ], ) async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None: - exists = self.check_cache(ctx, state=state) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return await ctx.defer() m_state = state if m_state == "all": @@ -394,15 +369,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "state": state, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( @@ -412,14 +378,6 @@ class GitlabCog(CacheCog): scopes=guild_ids, ) async def _milestones(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 await ctx.defer() milestones = [] page = 1 @@ -450,14 +408,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 83aff59..27df52e 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -13,6 +13,8 @@ from dis_snek.models.snek.cooldowns import Buckets from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize +MIN_ACCURACY = 0.80 + class ImageCog(Scale): """ @@ -64,8 +66,8 @@ class ImageCog(Scale): ratio = max(tgt_size / size - 0.02, 0.50) accuracy = 0.0 - # TODO: Optimize to not run multiple times - while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < 0.65): + + while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < MIN_ACCURACY): old_file = file buffer = np.frombuffer(file, dtype=np.uint8) diff --git a/jarvis/events/member.py b/jarvis/events/member.py index ee1043f..44a42c4 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -2,7 +2,7 @@ from dis_snek import Snake, listen from dis_snek.models.discord.user import Member -from jarvis.db.models import Mute, Setting +from jarvis.db.models import Setting class MemberEventHandler(object): @@ -16,11 +16,6 @@ class MemberEventHandler(object): async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild - mute = Mute.objects(guild=guild.id, user=user.id, active=True).first() - if mute: - mute_role = Setting.objects(guild=guild.id, setting="mute").first() - role = guild.get_role(mute_role.value) - await user.add_roles(role, reason="User is still muted from prior mute") unverified = Setting.objects(guild=guild.id, setting="unverified").first() if unverified: role = guild.get_role(unverified.value) diff --git a/jarvis/events/message.py b/jarvis/events/message.py index f62dc4a..0433cc4 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -8,7 +8,7 @@ from dis_snek.models.discord.message import Message from jarvis.config import get_config from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.utils import build_embed, find +from jarvis.utils import build_embed, find, find_all invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 @@ -150,13 +150,13 @@ class MessageEventHandler(object): roleping_ids = [r.role for r in rolepings] # Get roles in rolepings - role_in_rolepings = list(filter(lambda x: x in roleping_ids, roles)) + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) # Check if the user has the role, so they are allowed to ping it user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) # Admins can ping whoever - user_is_admin = message.author.guild_permissions.administrator + user_is_admin = message.author.guild_permissions.ADMINISTRATOR # Check if user in a bypass list user_has_bypass = False @@ -217,10 +217,3 @@ class MessageEventHandler(object): await self.checks(after) await self.roleping(after) await self.checks(after) - """Handle on_message_edit event. Calls other event handlers.""" - if not isinstance(after.channel, DMChannel) and not after.author.bot: - await self.massmention(after) - await self.roleping(after) - await self.checks(after) - await self.roleping(after) - await self.checks(after) diff --git a/jarvis/logo.py b/jarvis/logo.py deleted file mode 100644 index 79e550c..0000000 --- a/jarvis/logo.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Logos for J.A.R.V.I.S.""" - -logo_doom = r""" - ___ ___ ______ _ _ _____ _____ - |_ | / _ \ | ___ \ | | | | |_ _| / ___| - | | / /_\ \ | |_/ / | | | | | | \ `--. - | | | _ | | / | | | | | | `--. \ -/\__/ / _ | | | | _ | |\ \ _ \ \_/ / _ _| |_ _ /\__/ / _ -\____/ (_)\_| |_/(_)\_| \_|(_) \___/ (_) \___/ (_)\____/ (_) - -""" - -logo_epic = r""" -_________ _______ _______ _________ _______ -\__ _/ ( ___ ) ( ____ ) |\ /| \__ __/ ( ____ \ - ) ( | ( ) | | ( )| | ) ( | ) ( | ( \/ - | | | (___) | | (____)| | | | | | | | (_____ - | | | ___ | | __) ( ( ) ) | | (_____ ) - | | | ( ) | | (\ ( \ \_/ / | | ) | -|\_) ) _ | ) ( | _ | ) \ \__ _ \ / _ ___) (___ _ /\____) | _ -(____/ (_)|/ \|(_)|/ \__/(_) \_/ (_)\_______/(_)\_______)(_) - -""" - -logo_ivrit = r""" - _ _ ____ __ __ ___ ____ - | | / \ | _ \ \ \ / / |_ _| / ___| - _ | | / _ \ | |_) | \ \ / / | | \___ \ - | |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _ - \___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_) - -""" - -logo_kban = r""" - - '||' . | . '||''|. . '||' '|' . '||' . .|'''.| . - || ||| || || '|. .' || ||.. ' - || | || ||''|' || | || ''|||. - || .''''|. || |. ||| || . '|| -|| .|' .|. .||. .||. '|' | .||. |'....|' - ''' - -""" - -logo_larry3d = r""" - - _____ ______ ____ __ __ ______ ____ -/\___ \ /\ _ \ /\ _`\ /\ \/\ \ /\__ _\ /\ _`\ -\/__/\ \ \ \ \L\ \ \ \ \L\ \ \ \ \ \ \ \/_/\ \/ \ \,\L\_\ - _\ \ \ \ \ __ \ \ \ , / \ \ \ \ \ \ \ \ \/_\__ \ - /\ \_\ \ __ \ \ \/\ \ __ \ \ \\ \ __ \ \ \_/ \ __ \_\ \__ __ /\ \L\ \ __ - \ \____//\_\ \ \_\ \_\/\_\ \ \_\ \_\/\_\ \ `\___//\_\ /\_____\/\_\ \ `\____\/\_\ - \/___/ \/_/ \/_/\/_/\/_/ \/_/\/ /\/_/ `\/__/ \/_/ \/_____/\/_/ \/_____/\/_/ - -""" - -logo_slane = r""" - - __ ___ ____ _ __ ____ _____ - / / / | / __ \ | | / / / _/ / ___/ - __ / / / /| | / /_/ / | | / / / / \__ \ -/ /_/ / _ / ___ | _ / _, _/ _ | |/ / _ _/ / _ ___/ / _ -\____/ (_)/_/ |_|(_)/_/ |_| (_)|___/ (_)/___/ (_)/____/ (_) - -""" - -logo_standard = r""" - - _ _ ____ __ __ ___ ____ - | | / \ | _ \ \ \ / / |_ _| / ___| - _ | | / _ \ | |_) | \ \ / / | | \___ \ - | |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _ - \___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_) - -""" - -logo_alligator = r""" - - ::::::::::: ::: ::::::::: ::: ::: ::::::::::: :::::::: - :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: - +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ - +#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++ - +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ -#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# -##### ### ### ### ### ### ### ### ### ### ########### ### ######## ### - -""" # noqa: E501 - -logo_alligator2 = r""" - -::::::::::: ::: ::::::::: ::: ::: ::::::::::: :::::::: - :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: - +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ - +#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++ - +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ -#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# - ##### ### ### ### ### ### ### ### ### ### ########### ### ######## ### - -""" - - -def get_logo(lo: str) -> str: - """Get a logo.""" - if "logo_" not in lo: - lo = "logo_" + lo - return globals()[lo] if lo in globals() else logo_alligator2 From 0a9b8a3adcaf66549c95989bf1c82a2600dbddd2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 6 Feb 2022 01:28:20 -0700 Subject: [PATCH 04/34] Rework on_command, ref #108 --- jarvis/cogs/modlog/command.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index 75d54b5..f846f4c 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -1,40 +1,30 @@ """J.A.R.V.I.S. ModlogCommandCog.""" -from discord import DMChannel -from discord.ext import commands -from discord_slash import SlashContext +from dis_snek import InteractionContext, Snake, listen +from dis_snek.models.discord.channel import DMChannel +from dis_snek.models.discord.embed import EmbedField from jarvis.db.models import Setting from jarvis.utils import build_embed -from jarvis.utils.field import Field -class ModlogCommandCog(commands.Cog): +class ModlogCommandCog(object): """J.A.R.V.I.S. ModlogCommandCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot + self.bot.add_listener(self.on_command) - @commands.Cog.listener() - async def on_slash_command(self, ctx: SlashContext) -> None: + @listen() + async def on_command(self, ctx: InteractionContext) -> None: """Process on_slash_command events.""" if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]: modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first() if modlog: - channel = ctx.guild.get_channel(modlog.value) + channel = await ctx.guild.get_channel(modlog.value) + args = " ".join(f"{k}:v" for k, v in ctx.kwargs.items()) fields = [ - Field("Command", ctx.name), + EmbedField(name="Command", value=f"{ctx.invoked_name} {args}", inline=False), ] - if ctx.kwargs: - kwargs_string = " ".join(f"{k}: {str(ctx.kwargs[k])}" for k in ctx.kwargs) - fields.append( - Field( - "Keyword Args", - kwargs_string, - False, - ) - ) - if ctx.subcommand_name: - fields.insert(1, Field("Subcommand", ctx.subcommand_name)) embed = build_embed( title="Command Invoked", description=f"{ctx.author.mention} invoked a command", From 3d2dc03d70fa833e732c8ac19d3ddb6aa006cae0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 9 Feb 2022 14:18:56 -0700 Subject: [PATCH 05/34] Lots of abstraction, ref #121 --- jarvis/__init__.py | 2 +- jarvis/cogs/admin/ban.py | 12 +- jarvis/cogs/admin/purge.py | 5 +- jarvis/cogs/admin/roleping.py | 16 +-- jarvis/cogs/autoreact.py | 15 +-- jarvis/cogs/ctc2.py | 5 +- jarvis/cogs/dev.py | 70 +++++------ jarvis/cogs/jokes.py | 5 +- jarvis/cogs/owner.py | 9 +- jarvis/cogs/rolegiver.py | 18 +-- jarvis/cogs/settings.py | 12 +- jarvis/cogs/starboard.py | 10 +- jarvis/cogs/twitter.py | 15 +-- jarvis/cogs/verify.py | 10 +- jarvis/events/member.py | 6 +- jarvis/events/message.py | 57 +++++---- jarvis/tasks/reminder.py | 23 ++-- jarvis/tasks/twitter.py | 43 +++---- jarvis/tasks/unban.py | 49 +++----- jarvis/tasks/unwarn.py | 20 ++-- jarvis/utils/__init__.py | 122 +------------------ jarvis/utils/cachecog.py | 3 +- poetry.lock | 212 +++++++++++++++++++++++----------- pyproject.toml | 4 +- 24 files changed, 352 insertions(+), 391 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 9766919..c1537ad 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -24,7 +24,7 @@ restart_ctx = None jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) -__version__ = "2.0.0a0" +__version__ = "2.0.0a1" @listen() diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 4f3cdf4..f1cd3a8 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -3,6 +3,7 @@ import re from datetime import datetime, timedelta 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 from dis_snek.models.discord.user import User @@ -13,9 +14,10 @@ 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 Ban, Unban -from jarvis.db.models import Ban, Unban -from jarvis.utils import build_embed, find, find_all +from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog from jarvis.utils.permissions import admin_or_permissions @@ -242,7 +244,9 @@ class BanCog(CacheCog): # We take advantage of the previous checks to save CPU cycles if not discord_ban_info: if isinstance(user, int): - database_ban_info = Ban.objects(guild=ctx.guild.id, user=user, active=True).first() + database_ban_info = await Ban.find_one( + q(guild=ctx.guild.id, user=user, active=True) + ) else: search = { "guild": ctx.guild.id, @@ -251,7 +255,7 @@ class BanCog(CacheCog): } if discrim: search["discrim"] = discrim - database_ban_info = Ban.objects(**search).first() + database_ban_info = await Ban.find_one(q(**search)) if not discord_ban_info and not database_ban_info: await ctx.send(f"Unable to find user {orig_user}", ephemeral=True) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 78a2c46..4a893fb 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -7,8 +7,9 @@ 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 Autopurge, Purge -from jarvis.db.models import Autopurge, Purge from jarvis.utils.permissions import admin_or_permissions @@ -72,7 +73,7 @@ class PurgeCog(Scale): await ctx.send("Delay must be < 5 minutes", ephemeral=True) return - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first() + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if autopurge: await ctx.send("Autopurge already exists.", ephemeral=True) return diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 4d5fb70..57e40dc 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. RolepingCog.""" from dis_snek import InteractionContext, Permissions, Scale +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 from dis_snek.models.discord.role import Role @@ -10,9 +11,10 @@ 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 Roleping -from jarvis.db.models import Roleping -from jarvis.utils import build_embed, find_all +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -25,7 +27,7 @@ class RolepingCog(Scale): @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if roleping: await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return @@ -123,7 +125,7 @@ class RolepingCog(Scale): async def _roleping_bypass_user( self, ctx: InteractionContext, user: Member, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -164,7 +166,7 @@ class RolepingCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -203,7 +205,7 @@ class RolepingCog(Scale): async def _roleping_restore_user( self, ctx: InteractionContext, user: Member, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -232,7 +234,7 @@ class RolepingCog(Scale): async def _roleping_restore_role( self, ctx: InteractionContext, role: Role, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 78d403b..d5d127b 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -3,6 +3,7 @@ import re from typing import Optional, Tuple 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.snek.application_commands import ( OptionTypes, @@ -10,10 +11,10 @@ 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 Autoreact from jarvis.data.unicode import emoji_list -from jarvis.db.models import Autoreact -from jarvis.utils import find from jarvis.utils.permissions import admin_or_permissions @@ -37,7 +38,7 @@ class AutoReactCog(Scale): Returns: Tuple of success? and error message """ - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if exists: return False, f"Autoreact already exists for {channel.mention}." @@ -93,10 +94,10 @@ class AutoReactCog(Scale): 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) return - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: self.create_autoreact(ctx, channel) - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", @@ -134,7 +135,7 @@ class AutoReactCog(Scale): async def _autoreact_remove( self, ctx: InteractionContext, channel: GuildText, emote: str ) -> None: - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} {emote}", @@ -169,7 +170,7 @@ class AutoReactCog(Scale): required=True, ) async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None: - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not exists: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} ", diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index d509f0f..67f9d20 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -10,8 +10,9 @@ from dis_snek.models.discord.user import Member, User 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 +from jarvis_core.db.models import Guess -from jarvis.db.models import Guess from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog @@ -74,7 +75,7 @@ class CTCCog(CacheCog): ephemeral=True, ) return - guessed = Guess.objects(guess=guess).first() + guessed = await Guess.find_one(q(guess=guess)) if guessed: await ctx.send("Already guessed, dipshit.", ephemeral=True) return diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 98f2db2..ab26c05 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -4,12 +4,12 @@ import hashlib import re import subprocess # noqa: S404 import uuid as uuidpy -from typing import Any, Union import ulid as ulidpy from bson import ObjectId from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.message import Attachment from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -18,8 +18,11 @@ 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_core.filters import invites, url +from jarvis_core.util import convert_bytesize, hash +from jarvis_core.util.http import get_size -from jarvis.utils import build_embed, convert_bytesize +from jarvis.utils import build_embed supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x} @@ -36,29 +39,9 @@ UUID_VERIFY = re.compile( re.IGNORECASE, ) -invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 - flags=re.IGNORECASE, -) - UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5} - -def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str: - """Hash data with hash object. - - Data can be text or binary - """ - if text: - hash.update(data.encode("UTF-8")) - return hash.hexdigest() - BSIZE = 65536 - block_idx = 0 - while block_idx * BSIZE < len(data): - block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] - hash.update(block) - block_idx += 1 - return hash.hexdigest() +MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): @@ -76,26 +59,47 @@ class DevCog(Scale): name="data", description="Data to hash", opt_type=OptionTypes.STRING, - required=True, + required=False, + ) + @slash_option( + name="attach", description="File to hash", opt_type=OptionTypes.ATTACHMENT, required=False ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) - async def _hash(self, ctx: InteractionContext, method: str, data: str) -> None: - if not data: + async def _hash( + self, ctx: InteractionContext, method: str, data: str = None, attach: Attachment = None + ) -> None: + if not data and not attach: await ctx.send( "No data to hash", ephemeral=True, ) return - text = True - # Default to sha256, just in case - hash = getattr(hashlib, method, hashlib.sha256)() - hex = hash_obj(hash, data, text) - data_size = convert_bytesize(len(data)) - title = data if text else ctx.message.attachments[0].filename + if data and invites.match(data): + await ctx.send("No hashing invites", ephemeral=True) + return + title = data + if attach: + data = attach.url + title = attach.filename + elif url.match(data): + if await get_size(data) > MAX_FILESIZE: + await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + return + title = data.split("/")[-1] + + await ctx.defer() + try: + hexstr, size, c_type = await hash(data, method) + except Exception as e: + await ctx.send(f"Failed to hash data: ```\n{e}\n```", ephemeral=True) + return + + data_size = convert_bytesize(size) description = "Hashed using " + method fields = [ + EmbedField("Content Type", c_type, False), EmbedField("Data Size", data_size, False), - EmbedField("Hash", f"`{hex}`", False), + EmbedField("Hash", f"`{hexstr}`", False), ] embed = build_embed(title=title, description=description, fields=fields) diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py index 0bf3fcc..7697d37 100644 --- a/jarvis/cogs/jokes.py +++ b/jarvis/cogs/jokes.py @@ -14,8 +14,9 @@ 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_core.db import q +from jarvis_core.db.models import Joke -from jarvis.db.models import Joke from jarvis.utils import build_embed @@ -46,7 +47,7 @@ class JokeCog(Scale): threshold = 500 # Minimum score result = None if id: - result = Joke.objects(rid=id).first() + result = await Joke.find_one(q(rid=id)) else: pipeline = [ {"$match": {"score": {"$gt": threshold}}}, diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py index c00118e..094faf4 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -4,8 +4,7 @@ from dis_snek.models.discord.user import User from dis_snek.models.snek.checks import is_owner from dis_snek.models.snek.command import check -from jarvis.config import reload_config -from jarvis.db.models import Config +from jarvis import jconfig class OwnerCog(Scale): @@ -17,7 +16,7 @@ class OwnerCog(Scale): def __init__(self, bot: Snake): self.bot = bot - self.admins = Config.objects(key="admins").first() + # self.admins = await Config.find_one(q(key="admins")) @message_command(name="addadmin") @check(is_owner()) @@ -27,7 +26,7 @@ class OwnerCog(Scale): return self.admins.value.append(user.id) self.admins.save() - reload_config() + jconfig.reload() await ctx.send(f"{user.mention} is now an admin. Use this power carefully.") @message_command(name="deladmin") @@ -38,7 +37,7 @@ class OwnerCog(Scale): return self.admins.value.remove(user.id) self.admins.save() - reload_config() + jconfig.reload() await ctx.send(f"{user.mention} is no longer an admin.") diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 8b87241..7a115b9 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -2,6 +2,7 @@ import asyncio from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -12,9 +13,10 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import check, cooldown from dis_snek.models.snek.cooldowns import Buckets +from jarvis_core.db import q +from jarvis_core.db.models import Rolegiver -from jarvis.db.models import Rolegiver -from jarvis.utils import build_embed, get +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -30,7 +32,7 @@ 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: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + 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) return @@ -80,7 +82,7 @@ class RolegiverCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_remove(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -162,7 +164,7 @@ class RolegiverCog(Scale): @slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -198,7 +200,7 @@ class RolegiverCog(Scale): @slash_command(name="role", 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 = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -276,7 +278,7 @@ class RolegiverCog(Scale): async def _role_remove(self, ctx: InteractionContext) -> None: user_roles = ctx.author.roles - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -355,7 +357,7 @@ class RolegiverCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or not setting.roles: await ctx.send("Rolegiver has no roles", ephemeral=True) guild_role_ids = [r.id for r in ctx.guild.roles] diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 52da039..d761bc1 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -7,6 +7,7 @@ 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 +from jarvis_core.db import q from jarvis.db.models import Setting from jarvis.utils import build_embed @@ -20,9 +21,9 @@ class SettingsCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - def update_settings(self, setting: str, value: Any, guild: int) -> bool: + async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" - existing = Setting.objects(setting=setting, guild=guild).first() + existing = await Setting.find_one(q(setting=setting, guild=guild)) if not existing: existing = Setting(setting=setting, guild=guild, value=value) existing.value = value @@ -30,9 +31,12 @@ class SettingsCog(commands.Cog): return updated is not None - def delete_settings(self, setting: str, guild: int) -> bool: + async def delete_settings(self, setting: str, guild: int) -> bool: """Delete a guild setting.""" - return Setting.objects(setting=setting, guild=guild).delete() + existing = await Setting.find_one(q(setting=setting, guild=guild)) + if existing: + return await existing.delete() + return False @cog_ext.cog_subcommand( base="settings", diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 79442e6..a70d03a 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Starboard Cog.""" 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 @@ -11,9 +12,10 @@ 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 Star, Starboard -from jarvis.db.models import Star, Starboard -from jarvis.utils import build_embed, find +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions supported_images = [ @@ -64,7 +66,7 @@ class StarboardCog(Scale): await ctx.send("Channel must be a GuildText", ephemeral=True) return - exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first() + exists = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id)) if exists: await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True) return @@ -248,7 +250,7 @@ class StarboardCog(Scale): if not isinstance(starboard, GuildText): await ctx.send("Channel must be a GuildText channel", ephemeral=True) return - exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first() + exists = await Starboard.find_one(q(channel=starboard.id, guild=ctx.guild.id)) if not exists: await ctx.send( f"Starboard does not exist in {starboard.mention}. Please create it first", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index bd92ddc..7d16b92 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -13,9 +13,10 @@ 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 Twitter -from jarvis.config import get_config -from jarvis.db.models import Twitter +from jarvis import jconfig from jarvis.utils.permissions import admin_or_permissions @@ -24,7 +25,7 @@ class TwitterCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = get_config() + config = jconfig auth = tweepy.AppAuthHandler( config.twitter["consumer_key"], config.twitter["consumer_secret"] ) @@ -75,12 +76,12 @@ class TwitterCog(Scale): ) return - count = Twitter.objects(guild=ctx.guild.id).count() + count = len([i async for i in Twitter.find(guild=ctx.guild.id)]) if count >= 12: await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) return - exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id) + exists = Twitter.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) if exists: await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return @@ -95,7 +96,7 @@ class TwitterCog(Scale): retweets=retweets, ) - t.save() + await t.commit() await ctx.send(f"Now following `@{handle}` in {channel.mention}") @@ -194,7 +195,7 @@ class TwitterCog(Scale): handlemap = {str(x.id): x.handle for x in twitters} for to_update in context.context.values: - t = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first() + t = await Twitter.find_one(q(guild=ctx.guild.id, id=ObjectId(to_update)))() t.retweets = retweets t.save() diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 2d1db38..2255623 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -7,8 +7,8 @@ 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.command import cooldown from dis_snek.models.snek.cooldowns import Buckets - -from jarvis.db.models import Setting +from jarvis_core.db import q +from jarvis_core.db.models import Setting def create_layout() -> list: @@ -39,7 +39,7 @@ class VerifyCog(Scale): @cooldown(bucket=Buckets.USER, rate=1, interval=15) async def _verify(self, ctx: InteractionContext) -> None: await ctx.defer() - role = Setting.objects(guild=ctx.guild.id, setting="verified").first() + 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) return @@ -62,10 +62,10 @@ class VerifyCog(Scale): for row in components: for component in row.components: component.disabled = True - setting = Setting.objects(guild=ctx.guild.id, setting="verified").first() + 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 = Setting.objects(guild=ctx.guild.id, setting="unverified").first() + 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") diff --git a/jarvis/events/member.py b/jarvis/events/member.py index 44a42c4..f7e4ada 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. Member event handler.""" from dis_snek import Snake, listen from dis_snek.models.discord.user import Member - -from jarvis.db.models import Setting +from jarvis_core.db import q +from jarvis_core.db.models import Setting class MemberEventHandler(object): @@ -16,7 +16,7 @@ class MemberEventHandler(object): async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild - unverified = Setting.objects(guild=guild.id, setting="unverified").first() + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: role = guild.get_role(unverified.value) if role not in user.roles: diff --git a/jarvis/events/message.py b/jarvis/events/message.py index 0433cc4..693bc3c 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -2,13 +2,15 @@ import re from dis_snek import Snake, listen +from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.message import Message +from jarvis_core.db import q +from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.config import get_config -from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.utils import build_embed, find, find_all +import jarvis +from jarvis.utils import build_embed invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 @@ -26,16 +28,18 @@ class MessageEventHandler(object): async def autopurge(self, message: Message) -> None: """Handle autopurge events.""" - autopurge = Autopurge.objects(guild=message.guild.id, channel=message.channel.id).first() + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) if autopurge: await message.delete(delay=autopurge.delay) async def autoreact(self, message: Message) -> None: """Handle autoreact events.""" - autoreact = Autoreact.objects( - guild=message.guild.id, - channel=message.channel.id, - ).first() + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) if autoreact: for reaction in autoreact.reactions: await message.add_reaction(reaction) @@ -50,10 +54,10 @@ class MessageEventHandler(object): ) content = re.sub(r"\s+", "", message.content) match = invites.search(content) - setting = Setting.objects(guild=message.guild.id, setting="noinvite").first() + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) if not setting: setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - setting.save() + await setting.commit() if match: guild_invites = await message.guild.invites() allowed = [x.code for x in guild_invites] + [ @@ -63,14 +67,15 @@ class MessageEventHandler(object): ] if match.group(1) not in allowed and setting.value: await message.delete() - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Sent an invite link", user=message.author.id, - ).save() + ) + await w.commit() fields = [ EmbedField( name="Reason", @@ -94,10 +99,12 @@ class MessageEventHandler(object): async def massmention(self, message: Message) -> None: """Handle massmention events.""" - massmention = Setting.objects( - guild=message.guild.id, - setting="massmention", - ).first() + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) if ( massmention and massmention.value > 0 # noqa: W503 @@ -105,14 +112,15 @@ class MessageEventHandler(object): - (1 if message.author in message.mentions else 0) # noqa: W503 > massmention.value # noqa: W503 ): - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Mass Mention", user=message.author.id, - ).save() + ) + await w.commit() fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] embed = build_embed( title="Warning", @@ -130,7 +138,7 @@ class MessageEventHandler(object): async def roleping(self, message: Message) -> None: """Handle roleping events.""" - rolepings = Roleping.objects(guild=message.guild.id, active=True) + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) if not rolepings: return @@ -169,14 +177,15 @@ class MessageEventHandler(object): break if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Pinged a blocked role/user with a blocked role", user=message.author.id, - ).save() + ) + await w.commit() fields = [ EmbedField( name="Reason", diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index b897e8e..98385ab 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -1,23 +1,24 @@ """J.A.R.V.I.S. reminder background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Reminder import jarvis -from jarvis.db.models import Reminder from jarvis.utils import build_embed -async def _remind() -> None: - """J.A.R.V.I.S. reminder blocking task.""" - reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30)) - for reminder in reminders: +@Task.create(trigger=IntervalTrigger(seconds=15)) +async def remind() -> None: + """J.A.R.V.I.S. reminder background task.""" + reminders = Reminder.find(q(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))) + async for reminder in reminders: if reminder.remind_at <= datetime.utcnow(): user = await jarvis.jarvis.fetch_user(reminder.user) if not user: - reminder.delete() + await reminder.delete() continue embed = build_embed( title="You have a reminder", @@ -41,10 +42,4 @@ async def _remind() -> None: "but I couldn't send it to you." ) finally: - reminder.delete() - - -@Task.create(trigger=IntervalTrigger(seconds=15)) -async def remind() -> None: - """J.A.R.V.I.S. reminder background task.""" - await to_thread(_remind) + await reminder.delete() diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py index 46638f5..ff85def 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -1,41 +1,40 @@ """J.A.R.V.I.S. twitter background task handler.""" import logging -from asyncio import to_thread from datetime import datetime, timedelta import tweepy from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Twitter import jarvis -from jarvis.config import get_config -from jarvis.db.models import Twitter logger = logging.getLogger("jarvis") -__auth = tweepy.AppAuthHandler( - get_config().twitter["consumer_key"], get_config().twitter["consumer_secret"] -) -__api = tweepy.API(__auth) - -async def _tweets() -> None: - """J.A.R.V.I.S. twitter blocking task.""" +@Task.create(trigger=IntervalTrigger(minutes=1)) +async def tweets() -> None: + """J.A.R.V.I.S. twitter background task.""" + config = jarvis.config.get_config() + __auth = tweepy.AppAuthHandler( + config.twitter["consumer_key"], jarvis.jconfig.twitter["consumer_secret"] + ) + __api = tweepy.API(__auth) guild_cache = {} channel_cache = {} - twitters = Twitter.objects(active=True) - for twitter in twitters: + twitters = Twitter.find(q(active=True)) + async for twitter in twitters: try: if not twitter.twitter_id or not twitter.last_sync: user = __api.get_user(screen_name=twitter.handle) - twitter.twitter_id = user.id - twitter.handle = user.screen_name - twitter.last_sync = datetime.now() + twitter.update( + q(twitter_id=user.id, handle=user.screen_name, last_sync=datetime.now()) + ) if twitter.last_sync + timedelta(hours=1) <= datetime.now(): user = __api.get_user(id=twitter.twitter_id) - twitter.handle = user.screen_name - twitter.last_sync = datetime.now() + twitter.update(q(handle=user.screen_name, last_sync=datetime.now())) if tweets := __api.user_timeline(id=twitter.twitter_id): guild_id = twitter.guild @@ -58,13 +57,7 @@ async def _tweets() -> None: f"`@{twitter.handle}` {verb}tweeted this at : {url}" ) newest = max(tweets, key=lambda x: x.id) - twitter.last_tweet = newest.id - twitter.save() + twitter.update(q(last_tweet=newest.id)) + await twitter.commit() except Exception as e: logger.error(f"Error with tweets: {e}") - - -@Task.create(trigger=IntervalTrigger(minutes=1)) -async def tweets() -> None: - """J.A.R.V.I.S. twitter background task.""" - await to_thread(_tweets) diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index f5b2849..70c2b60 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -1,46 +1,33 @@ """J.A.R.V.I.S. unban background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Ban, Unban import jarvis -from jarvis.config import get_config -from jarvis.db.models import Ban, Unban - -jarvis_id = get_config().client_id +@Task.create(IntervalTrigger(minutes=10)) async def _unban() -> None: - """J.A.R.V.I.S. unban blocking task.""" - bans = Ban.objects(type="temp", active=True) - unbans = [] - for ban in bans: - if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta( - minutes=10 - ): + """J.A.R.V.I.S. unban background task.""" + jarvis_id = jarvis.jconfig.client_id + bans = Ban.find(q(type="temp", active=True)) + async for ban in bans: + if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): guild = await jarvis.jarvis.get_guild(ban.guild) user = await jarvis.jarvis.get_user(ban.user) if user: await guild.unban(user=user, reason="Ban expired") - ban.active = False - ban.save() - unbans.append( - Unban( - user=user.id, - guild=guild.id, - username=user.name, - discrim=user.discriminator, - admin=jarvis_id, - reason="Ban expired", - ) + ban.update(q(active=False)) + await ban.commit() + u = Unban( + user=user.id, + guild=guild.id, + username=user.name, + discrim=user.discriminator, + admin=jarvis_id, + reason="Ban expired", ) - if unbans: - Unban.objects().insert(unbans) - - -@Task.create(IntervalTrigger(minutes=10)) -async def unban() -> None: - """J.A.R.V.I.S. unban background task.""" - await to_thread(_unban) + await u.commit() diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py index 0af40a1..c3afdac 100644 --- a/jarvis/tasks/unwarn.py +++ b/jarvis/tasks/unwarn.py @@ -1,23 +1,17 @@ """J.A.R.V.I.S. unwarn background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger - -from jarvis.db.models import Warning - - -async def _unwarn() -> None: - """J.A.R.V.I.S. unwarn blocking task.""" - warns = Warning.objects(active=True) - for warn in warns: - if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow(): - warn.active = False - warn.save() +from jarvis_core.db import q +from jarvis_core.db.models import Warning @Task.create(IntervalTrigger(hours=1)) async def unwarn() -> None: """J.A.R.V.I.S. unwarn background task.""" - await to_thread(_unwarn) + warns = Warning.find(q(active=True)) + async for warn in warns: + if warn.created_at + timedelta(hours=warn.duration) < datetime.now(): + warn.update(q(active=False)) + await warn.commit() diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index c5d8b19..009d24b 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,7 +1,6 @@ """J.A.R.V.I.S. Utility Functions.""" from datetime import datetime from pkgutil import iter_modules -from typing import Any, Callable, Iterable, List, Optional, TypeVar import git from dis_snek.models.discord.embed import Embed @@ -10,9 +9,7 @@ import jarvis.cogs import jarvis.db from jarvis.config import get_config -__all__ = ["field", "db", "cachecog", "permissions"] - -T = TypeVar("T") +__all__ = ["cachecog", "permissions"] def build_embed( @@ -38,27 +35,6 @@ def build_embed( return embed -def convert_bytesize(b: int) -> str: - """Convert bytes amount to human readable.""" - b = float(b) - sizes = ["B", "KB", "MB", "GB", "TB", "PB"] - size = 0 - while b >= 1024 and size < len(sizes) - 1: - b = b / 1024 - size += 1 - return "{:0.3f} {}".format(b, sizes[size]) - - -def unconvert_bytesize(size: int, ending: str) -> int: - """Convert human readable to bytes.""" - ending = ending.upper() - sizes = ["B", "KB", "MB", "GB", "TB", "PB"] - if ending == "B": - return size - # Rounding is only because bytes cannot be partial - return round(size * (1024 ** sizes.index(ending))) - - def get_extensions(path: str = jarvis.cogs.__path__) -> list: """Get J.A.R.V.I.S. cogs.""" config = get_config() @@ -85,99 +61,3 @@ def get_repo_hash() -> str: """J.A.R.V.I.S. current branch hash.""" repo = git.Repo(".") return repo.head.object.hexsha - - -def find(predicate: Callable, sequence: Iterable) -> Optional[Any]: - """ - Find the first element in a sequence that matches the predicate. - - ??? Hint "Example Usage:" - ```python - member = find(lambda m: m.name == "UserName", guild.members) - ``` - Args: - predicate: A callable that returns a boolean value - sequence: A sequence to be searched - - Returns: - A match if found, otherwise None - - """ - for el in sequence: - if predicate(el): - return el - return None - - -def find_all(predicate: Callable, sequence: Iterable) -> List[Any]: - """ - Find all elements in a sequence that match the predicate. - - ??? Hint "Example Usage:" - ```python - members = find_all(lambda m: m.name == "UserName", guild.members) - ``` - Args: - predicate: A callable that returns a boolean value - sequence: A sequence to be searched - - Returns: - A list of matches - - """ - return [el for el in sequence if predicate(el)] - - -def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]: - """ - Find the first element in a sequence that matches all attrs. - - ??? Hint "Example Usage:" - ```python - channel = get(guild.channels, nsfw=False, category="General") - ``` - - Args: - sequence: A sequence to be searched - kwargs: Keyword arguments to search the sequence for - - Returns: - A match if found, otherwise None - """ - if not kwargs: - return sequence[0] - - for el in sequence: - if any(not hasattr(el, attr) for attr in kwargs.keys()): - continue - if all(getattr(el, attr) == value for attr, value in kwargs.items()): - return el - return None - - -def get_all(sequence: Iterable, **kwargs: Any) -> List[Any]: - """ - Find all elements in a sequence that match all attrs. - - ??? Hint "Example Usage:" - ```python - channels = get_all(guild.channels, nsfw=False, category="General") - ``` - - Args: - sequence: A sequence to be searched - kwargs: Keyword arguments to search the sequence for - - Returns: - A list of matches - """ - if not kwargs: - return sequence - - matches = [] - for el in sequence: - if any(not hasattr(el, attr) for attr in kwargs.keys()): - continue - if all(getattr(el, attr) == value for attr, value in kwargs.items()): - matches.append(el) - return matches diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py index 9497bac..d7be74b 100644 --- a/jarvis/utils/cachecog.py +++ b/jarvis/utils/cachecog.py @@ -2,11 +2,10 @@ 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 -from jarvis.utils import find - class CacheCog(Scale): """Cog wrapper for command caching.""" diff --git a/poetry.lock b/poetry.lock index f59cc4b..af0717b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -136,7 +136,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dis-snek" -version = "5.0.0" +version = "6.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -212,6 +212,28 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] +[[package]] +name = "jarvis-core" +version = "0.2.1" +description = "" +category = "main" +optional = false +python-versions = "^3.10" +develop = false + +[package.dependencies] +dis-snek = "*" +motor = "^2.5.1" +orjson = "^3.6.6" +PyYAML = "^6.0" +umongo = "^3.1.0" + +[package.source] +type = "git" +url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" +reference = "main" +resolved_reference = "0e627eae725abb1e6f3766c5dc94bd80d0ac6702" + [[package]] name = "jedi" version = "0.18.1" @@ -235,6 +257,20 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "marshmallow" +version = "3.14.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.6.1" @@ -254,6 +290,20 @@ python-versions = ">=3.6" [package.dependencies] pymongo = ">=3.4,<4.0" +[[package]] +name = "motor" +version = "2.5.1" +description = "Non-blocking MongoDB driver for Tornado or asyncio" +category = "main" +optional = false +python-versions = ">=3.5.2" + +[package.dependencies] +pymongo = ">=3.12,<4" + +[package.extras] +encryption = ["pymongo[encryption] (>=3.12,<4)"] + [[package]] name = "multidict" version = "6.0.2" @@ -272,7 +322,7 @@ python-versions = "*" [[package]] name = "numpy" -version = "1.22.1" +version = "1.22.2" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -337,7 +387,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pillow" -version = "9.0.0" +version = "9.0.1" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -345,7 +395,7 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -597,7 +647,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "main" optional = false @@ -637,6 +687,23 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "umongo" +version = "3.1.0" +description = "sync/async MongoDB ODM, yes." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +marshmallow = ">=3.10.0" +pymongo = ">=3.7.0" + +[package.extras] +mongomock = ["mongomock"] +motor = ["motor (>=2.0,<3.0)"] +txmongo = ["txmongo (>=19.2.0)"] + [[package]] name = "urllib3" version = "1.26.8" @@ -681,7 +748,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "8a1e6e29ff70363abddad36082a494c4ce1f9cc672fe7aff30b6d5b596d50dac" +content-hash = "d34963008bb31a5168210290f44909aa684a363455b2d59fe792f41918ec4705" [metadata.files] aiohttp = [ @@ -820,8 +887,8 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dis-snek = [ - {file = "dis-snek-5.0.0.tar.gz", hash = "sha256:cc733b510d6b20523a8067f19b6d9b99804c13e37d78cd6e0fc401098adbca27"}, - {file = "dis_snek-5.0.0-py3-none-any.whl", hash = "sha256:d1d50ba468ad6b0788e9281eb9d83f6eb2f8d964c1212ccd0e3fb33295462263"}, + {file = "dis-snek-6.0.0.tar.gz", hash = "sha256:3abe2af832bd87adced01ebc697e418b25871a4158ac8ba2e202d8fbb2921f44"}, + {file = "dis_snek-6.0.0-py3-none-any.whl", hash = "sha256:106cb08b0cc982c59db31be01ee529e4d7273835de7f418562d8e6b24d7f965d"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -904,6 +971,7 @@ 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"}, @@ -947,6 +1015,10 @@ lazy-object-proxy = [ {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.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, + {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -955,6 +1027,10 @@ mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, ] +motor = [ + {file = "motor-2.5.1-py3-none-any.whl", hash = "sha256:961fdceacaae2c7236c939166f66415be81be8bbb762da528386738de3a0f509"}, + {file = "motor-2.5.1.tar.gz", hash = "sha256:663473f4498f955d35db7b6f25651cb165514c247136f368b84419cb7635f6b8"}, +] multidict = [ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, @@ -1021,28 +1097,25 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] numpy = [ - {file = "numpy-1.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d62d6b0870b53799204515145935608cdeb4cebb95a26800b6750e48884cc5b"}, - {file = "numpy-1.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:831f2df87bd3afdfc77829bc94bd997a7c212663889d56518359c827d7113b1f"}, - {file = "numpy-1.22.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d1563060e77096367952fb44fca595f2b2f477156de389ce7c0ade3aef29e21"}, - {file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69958735d5e01f7b38226a6c6e7187d72b7e4d42b6b496aca5860b611ca0c193"}, - {file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45a7dfbf9ed8d68fd39763940591db7637cf8817c5bce1a44f7b56c97cbe211e"}, - {file = "numpy-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:7e957ca8112c689b728037cea9c9567c27cf912741fabda9efc2c7d33d29dfa1"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:800dfeaffb2219d49377da1371d710d7952c9533b57f3d51b15e61c4269a1b5b"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:65f5e257987601fdfc63f1d02fca4d1c44a2b85b802f03bd6abc2b0b14648dd2"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:632e062569b0fe05654b15ef0e91a53c0a95d08ffe698b66f6ba0f927ad267c2"}, - {file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d245a2bf79188d3f361137608c3cd12ed79076badd743dc660750a9f3074f7c"}, - {file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b4018a19d2ad9606ce9089f3d52206a41b23de5dfe8dc947d2ec49ce45d015"}, - {file = "numpy-1.22.1-cp38-cp38-win32.whl", hash = "sha256:f8ad59e6e341f38266f1549c7c2ec70ea0e3d1effb62a44e5c3dba41c55f0187"}, - {file = "numpy-1.22.1-cp38-cp38-win_amd64.whl", hash = "sha256:60f19c61b589d44fbbab8ff126640ae712e163299c2dd422bfe4edc7ec51aa9b"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2db01d9838a497ba2aa9a87515aeaf458f42351d72d4e7f3b8ddbd1eba9479f2"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bcd19dab43b852b03868796f533b5f5561e6c0e3048415e675bec8d2e9d286c1"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78bfbdf809fc236490e7e65715bbd98377b122f329457fffde206299e163e7f3"}, - {file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c51124df17f012c3b757380782ae46eee85213a3215e51477e559739f57d9bf6"}, - {file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d54b7b516f0ca38a69590557814de2dd638d7d4ed04864826acaac5ebb8f01"}, - {file = "numpy-1.22.1-cp39-cp39-win32.whl", hash = "sha256:b5ec9a5eaf391761c61fd873363ef3560a3614e9b4ead17347e4deda4358bca4"}, - {file = "numpy-1.22.1-cp39-cp39-win_amd64.whl", hash = "sha256:4ac4d7c9f8ea2a79d721ebfcce81705fc3cd61a10b731354f1049eb8c99521e8"}, - {file = "numpy-1.22.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e60ef82c358ded965fdd3132b5738eade055f48067ac8a5a8ac75acc00cad31f"}, - {file = "numpy-1.22.1.zip", hash = "sha256:e348ccf5bc5235fc405ab19d53bec215bb373300e5523c7b476cc0da8a5e9973"}, + {file = "numpy-1.22.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:515a8b6edbb904594685da6e176ac9fbea8f73a5ebae947281de6613e27f1956"}, + {file = "numpy-1.22.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76a4f9bce0278becc2da7da3b8ef854bed41a991f4226911a24a9711baad672c"}, + {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:168259b1b184aa83a514f307352c25c56af111c269ffc109d9704e81f72e764b"}, + {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3556c5550de40027d3121ebbb170f61bbe19eb639c7ad0c7b482cd9b560cd23b"}, + {file = "numpy-1.22.2-cp310-cp310-win_amd64.whl", hash = "sha256:aafa46b5a39a27aca566198d3312fb3bde95ce9677085efd02c86f7ef6be4ec7"}, + {file = "numpy-1.22.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:55535c7c2f61e2b2fc817c5cbe1af7cb907c7f011e46ae0a52caa4be1f19afe2"}, + {file = "numpy-1.22.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:60cb8e5933193a3cc2912ee29ca331e9c15b2da034f76159b7abc520b3d1233a"}, + {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b536b6840e84c1c6a410f3a5aa727821e6108f3454d81a5cd5900999ef04f89"}, + {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2638389562bda1635b564490d76713695ff497242a83d9b684d27bb4a6cc9d7a"}, + {file = "numpy-1.22.2-cp38-cp38-win32.whl", hash = "sha256:6767ad399e9327bfdbaa40871be4254d1995f4a3ca3806127f10cec778bd9896"}, + {file = "numpy-1.22.2-cp38-cp38-win_amd64.whl", hash = "sha256:03ae5850619abb34a879d5f2d4bb4dcd025d6d8fb72f5e461dae84edccfe129f"}, + {file = "numpy-1.22.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d76a26c5118c4d96e264acc9e3242d72e1a2b92e739807b3b69d8d47684b6677"}, + {file = "numpy-1.22.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15efb7b93806d438e3bc590ca8ef2f953b0ce4f86f337ef4559d31ec6cf9d7dd"}, + {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badca914580eb46385e7f7e4e426fea6de0a37b9e06bec252e481ae7ec287082"}, + {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dd11d9f13ea1be17bac39c1942f527cbf7065f94953cf62dfe805653da2f8f"}, + {file = "numpy-1.22.2-cp39-cp39-win32.whl", hash = "sha256:8cf33634b60c9cef346663a222d9841d3bbbc0a2f00221d6bcfd0d993d5543f6"}, + {file = "numpy-1.22.2-cp39-cp39-win_amd64.whl", hash = "sha256:59153979d60f5bfe9e4c00e401e24dfe0469ef8da6d68247439d3278f30a180f"}, + {file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"}, + {file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"}, ] oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, @@ -1092,42 +1165,45 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pillow = [ - {file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"}, - {file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"}, - {file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"}, - {file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"}, - {file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"}, - {file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"}, - {file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"}, - {file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"}, - {file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"}, - {file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"}, - {file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"}, - {file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"}, - {file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"}, + {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"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {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.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, + {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -1368,8 +1444,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tweepy = [ {file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"}, @@ -1431,6 +1507,10 @@ 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"}, ] +umongo = [ + {file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"}, + {file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"}, +] urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, diff --git a/pyproject.toml b/pyproject.toml index 7eee61c..a897256 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ authors = ["Zevaryx "] [tool.poetry.dependencies] python = "^3.10" PyYAML = "^6.0" -dis-snek = "^5.0.0" +dis-snek = "*" GitPython = "^3.1.26" mongoengine = "^0.23.1" opencv-python = "^4.5.5" @@ -17,6 +17,8 @@ python-gitlab = "^3.1.1" ulid-py = "^1.1.0" tweepy = "^4.5.0" orjson = "^3.6.6" +jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} +aiohttp = "^3.8.1" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 38c0a470860954cd56ee076db57539e9b82320b6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 16 Feb 2022 11:09:17 -0700 Subject: [PATCH 06/34] Fix dev cog, add error logging --- jarvis/__init__.py | 90 +++++++++++++++++++++++++++++++++++--------- jarvis/cogs/dev.py | 8 +++- jarvis/cogs/error.py | 56 --------------------------- 3 files changed, 79 insertions(+), 75 deletions(-) delete mode 100644 jarvis/cogs/error.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index c1537ad..733cdbb 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,8 +1,12 @@ """Main J.A.R.V.I.S. package.""" +import json import logging +import traceback +from datetime import datetime -from dis_snek import Intents, Snake, listen -from mongoengine import connect +from aiohttp import ClientSession +from dis_snek import Context, Intents, Snake, listen +from jarvis_core.db import connect # from jarvis import logo # noqa: F401 from jarvis import tasks, utils @@ -17,12 +21,75 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) logger.addHandler(file_handler) -intents = Intents.DEFAULT -intents.members = True +intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS restart_ctx = None +DEFAULT_GUILD = 862402786116763668 +DEFAULT_ERROR_CHANNEL = 943395824560394250 +DEFAULT_URL = "https://paste.zevs.me/documents" -jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) +ERROR_MSG = """ +Command Information: + Name: {invoked_name} + Args: +{arg_str} + +Callback: + Args: +{callback_args} + Kwargs: +{callback_kwargs} +""" + + +class Jarvis(Snake): + async def on_command_error( + self, ctx: Context, error: Exception, *args: list, **kwargs: dict + ) -> None: + """Lepton on_command_error override.""" + guild = await jarvis.get_guild(DEFAULT_GUILD) + channel = guild.get_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" + ) + 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" + ) + full_message = ERROR_MSG.format( + error_time=error_time, + invoked_name=ctx.invoked_name, + arg_str=arg_str, + callback_args=callback_args, + callback_kwargs=callback_kwargs, + ) + if len(full_message) >= 1900: + error_message = " ".join(traceback.format_exception(error)) + full_message += "Exception: |\n " + error_message + async with ClientSession() as session: + resp = await session.post(DEFAULT_URL, data=full_message) + data = await resp.read() + data = json.loads(data.decode("UTF8")) + + await channel.send( + f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." + f"\nPlease see log at https://paste.zevs.me/{data['key']}" + ) + 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```" + f"\nException:\n```py\n{error_message}\n```" + ) + await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) + return await super().on_command_error(ctx, error, *args, **kwargs) + + +jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) __version__ = "2.0.0a1" @@ -43,18 +110,7 @@ async def on_startup() -> None: def run() -> None: """Run J.A.R.V.I.S.""" - connect( - db="ctc2", - alias="ctc2", - authentication_source="admin", - **jconfig.mongo["connect"], - ) - connect( - db=jconfig.mongo["database"], - alias="main", - authentication_source="admin", - **jconfig.mongo["connect"], - ) + connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") jconfig.get_db_config() for extension in utils.get_extensions(): diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index ab26c05..f867100 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -82,8 +82,12 @@ class DevCog(Scale): data = attach.url title = attach.filename elif url.match(data): - if await get_size(data) > MAX_FILESIZE: - await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + try: + if await get_size(data) > MAX_FILESIZE: + await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + return + except Exception as e: + await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True) return title = data.split("/")[-1] diff --git a/jarvis/cogs/error.py b/jarvis/cogs/error.py deleted file mode 100644 index d245b0c..0000000 --- a/jarvis/cogs/error.py +++ /dev/null @@ -1,56 +0,0 @@ -"""J.A.R.V.I.S. error handling cog.""" -from discord.ext import commands -from discord_slash import SlashContext - -from jarvis import slash - - -class ErrorHandlerCog(commands.Cog): - """J.A.R.V.I.S. error handling cog.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.Cog.listener() - async def on_command_error(self, ctx: commands.Context, error: Exception) -> None: - """d.py on_command_error override.""" - if isinstance(error, commands.errors.MissingPermissions): - await ctx.send("I'm afraid I can't let you do that.") - elif isinstance(error, commands.errors.CommandNotFound): - return - elif isinstance(error, commands.errors.CommandOnCooldown): - await ctx.send( - "Command on cooldown. " - f"Please wait {error.retry_after:0.2f}s before trying again", - ) - else: - await ctx.send(f"Error processing command:\n```{error}```") - ctx.command.reset_cooldown(ctx) - - @commands.Cog.listener() - async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None: - """discord_slash on_slash_command_error override.""" - if isinstance(error, commands.errors.MissingPermissions) or isinstance( - error, commands.errors.CheckFailure - ): - await ctx.send("I'm afraid I can't let you do that.", ephemeral=True) - elif isinstance(error, commands.errors.CommandNotFound): - return - elif isinstance(error, commands.errors.CommandOnCooldown): - await ctx.send( - "Command on cooldown. " - f"Please wait {error.retry_after:0.2f}s before trying again", - ephemeral=True, - ) - else: - await ctx.send( - f"Error processing command:\n```{error}```", - ephemeral=True, - ) - raise error - slash.commands[ctx.command].reset_cooldown(ctx) - - -def setup(bot: commands.Bot) -> None: - """Add ErrorHandlerCog to J.A.R.V.I.S.""" - ErrorHandlerCog(bot) From b8d1cb6cfcbef3b0bc68bde39475e630fb1805b4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 13:39:31 -0700 Subject: [PATCH 07/34] Migrate to newer versions --- jarvis/cogs/admin/kick.py | 7 ++++--- jarvis/cogs/admin/mute.py | 5 +++-- jarvis/cogs/admin/purge.py | 18 ++++++++++-------- jarvis/cogs/remindme.py | 31 ++++++++++++++++++------------- jarvis/config.py | 14 ++++++++++++++ jarvis/tasks/reminder.py | 4 ++-- jarvis/tasks/twitter.py | 6 +++--- jarvis/tasks/unban.py | 8 ++++---- jarvis/tasks/unlock.py | 8 ++++---- jarvis/tasks/unwarn.py | 4 ++-- 10 files changed, 64 insertions(+), 41 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index eb09b2a..3bb7cfb 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -8,8 +8,8 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db.models import Kick -from jarvis.db.models import Kick from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -65,10 +65,11 @@ class KickCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - _ = Kick( + k = Kick( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, - ).save() + ) + await k.commit() await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 9733e6e..13aa948 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -74,14 +74,15 @@ class MuteCog(Scale): return await user.timeout(communication_disabled_until=duration, reason=reason) - _ = Mute( + m = Mute( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, active=True, - ).save() + ) + await m.commit() embed = build_embed( title="User Muted", diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 4a893fb..a5a73c6 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -37,12 +37,13 @@ class PurgeCog(Scale): async for message in ctx.channel.history(limit=amount + 1): messages.append(message) await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") - _ = Purge( + p = Purge( channel=ctx.channel.id, guild=ctx.guild.id, admin=ctx.author.id, count=amount, - ).save() + ) + await p.commit() @slash_command( name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages" @@ -78,12 +79,13 @@ class PurgeCog(Scale): await ctx.send("Autopurge already exists.", ephemeral=True) return - _ = Autopurge( + p = Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, - ).save() + ) + await p.commit() await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") @@ -98,11 +100,11 @@ class PurgeCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None: - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return - autopurge.delete() + await autopurge.delete() await ctx.send(f"Autopurge removed from {channel.mention}.") @slash_command( @@ -126,12 +128,12 @@ class PurgeCog(Scale): async def _autopurge_update( self, ctx: InteractionContext, channel: GuildText, delay: int ) -> None: - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return autopurge.delay = delay - autopurge.save() + await autopurge.commit() await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.") diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9639485..e7a90b9 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -6,6 +6,7 @@ from typing import List from bson import ObjectId from dis_snek import InteractionContext, Snake +from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField @@ -15,8 +16,9 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from jarvis_core.db import q +from jarvis_core.db.models import Reminder -from jarvis.db.models import Reminder from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog @@ -94,7 +96,7 @@ class RemindmeCog(CacheCog): await ctx.send("At least one time period is required", ephemeral=True) return - reminders = Reminder.objects(user=ctx.author.id, active=True).count() + reminders = len(await Reminder.find(q(user=ctx.author.id, active=True))) if reminders >= 5: await ctx.send( "You already have 5 (or more) active reminders. " @@ -105,7 +107,7 @@ class RemindmeCog(CacheCog): remind_at = datetime.now() + timedelta(**delta) - _ = Reminder( + r = Reminder( user=ctx.author_id, channel=ctx.channel.id, guild=ctx.guild.id, @@ -113,7 +115,9 @@ class RemindmeCog(CacheCog): remind_at=remind_at, private=private, active=True, - ).save() + ) + + await r.commit() embed = build_embed( title="Reminder Set", @@ -183,7 +187,7 @@ class RemindmeCog(CacheCog): ephemeral=True, ) return - reminders = Reminder.objects(user=ctx.author.id, active=True) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)) if not reminders: await ctx.send("You have no reminders set.", ephemeral=True) return @@ -194,7 +198,7 @@ class RemindmeCog(CacheCog): @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: - reminders = Reminder.objects(user=ctx.author.id, active=True) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)) if not reminders: await ctx.send("You have no reminders set", ephemeral=True) return @@ -230,15 +234,10 @@ class RemindmeCog(CacheCog): messages=message, timeout=60 * 5, ) - for to_delete in context.context.values: - _ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete() - - for row in components: - for component in row.components: - component.disabled = True fields = [] - for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): + for to_delete in context.context.values: + reminder = get(reminders, user=ctx.author.id, id=ObjectId(to_delete)) if reminder.private and isinstance(ctx.channel, GuildChannel): fields.append( EmbedField( @@ -255,6 +254,12 @@ class RemindmeCog(CacheCog): inline=False, ) ) + await reminder.delete() + + for row in components: + for component in row.components: + component.disabled = True + embed = build_embed( title="Deleted Reminder(s)", description="", diff --git a/jarvis/config.py b/jarvis/config.py index c2b9983..e8d65bd 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,6 +1,7 @@ """Load the config for J.A.R.V.I.S.""" import os +from jarvis_core.config import Config as CConfig from pymongo import MongoClient from yaml import load @@ -10,6 +11,19 @@ except ImportError: from yaml import Loader +class JarvisConfig(CConfig): + REQUIRED = ["token", "client_id", "mongo", "urls"] + OPTIONAL = { + "sync": False, + "log_level": "WARNING", + "scales": None, + "events": True, + "gitlab_token": None, + "max_messages": 1000, + "twitter": None, + } + + class Config(object): """Config singleton object for J.A.R.V.I.S.""" diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index 98385ab..eba3a95 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. reminder background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +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 Reminder diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py index ff85def..20a8403 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -3,8 +3,8 @@ import logging from datetime import datetime, timedelta import tweepy -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +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 Twitter @@ -41,7 +41,7 @@ async def tweets() -> None: channel_id = twitter.channel tweets = sorted(tweets, key=lambda x: x.id) if guild_id not in guild_cache: - guild_cache[guild_id] = await jarvis.jarvis.get_guild(guild_id) + guild_cache[guild_id] = await jarvis.jarvis.fetch_guild(guild_id) guild = guild_cache[twitter.guild] if channel_id not in channel_cache: channel_cache[channel_id] = await guild.fetch_channel(channel_id) diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index 70c2b60..55a8409 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. unban background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +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 Ban, Unban @@ -16,8 +16,8 @@ async def _unban() -> None: bans = Ban.find(q(type="temp", active=True)) async for ban in bans: if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): - guild = await jarvis.jarvis.get_guild(ban.guild) - user = await jarvis.jarvis.get_user(ban.user) + guild = await jarvis.jarvis.fetch_guild(ban.guild) + user = await jarvis.jarvis.fetch_user(ban.user) if user: await guild.unban(user=user, reason="Ban expired") ban.update(q(active=False)) diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py index 2d8793f..22f6f15 100644 --- a/jarvis/tasks/unlock.py +++ b/jarvis/tasks/unlock.py @@ -2,8 +2,8 @@ from asyncio import to_thread from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger import jarvis from jarvis.db.models import Lock @@ -17,8 +17,8 @@ async def _unlock() -> None: if False: for lock in locks: if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow(): - guild = await jarvis.jarvis.get_guild(lock.guild) - channel = await jarvis.jarvis.get_guild(lock.channel) + guild = await jarvis.jarvis.fetch_guild(lock.guild) + channel = await guild.fetch_channel(lock.channel) if channel: roles = await guild.fetch_roles() for role in roles: diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py index c3afdac..84a07db 100644 --- a/jarvis/tasks/unwarn.py +++ b/jarvis/tasks/unwarn.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. unwarn background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +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 Warning From fd97e82ed4ece15638c663c4d63b6b3424e78a60 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 17:04:43 -0700 Subject: [PATCH 08/34] Move tasks to jarvis-tasks --- jarvis/__init__.py | 8 +---- jarvis/tasks/__init__.py | 10 ------- jarvis/tasks/reminder.py | 45 ---------------------------- jarvis/tasks/twitter.py | 63 ---------------------------------------- jarvis/tasks/unban.py | 33 --------------------- jarvis/tasks/unlock.py | 37 ----------------------- jarvis/tasks/unwarn.py | 17 ----------- 7 files changed, 1 insertion(+), 212 deletions(-) delete mode 100644 jarvis/tasks/__init__.py delete mode 100644 jarvis/tasks/reminder.py delete mode 100644 jarvis/tasks/twitter.py delete mode 100644 jarvis/tasks/unban.py delete mode 100644 jarvis/tasks/unlock.py delete mode 100644 jarvis/tasks/unwarn.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 733cdbb..ebc6693 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -9,7 +9,7 @@ from dis_snek import Context, Intents, Snake, listen from jarvis_core.db import connect # from jarvis import logo # noqa: F401 -from jarvis import tasks, utils +from jarvis import utils from jarvis.config import get_config from jarvis.events import member, message @@ -102,12 +102,6 @@ async def on_ready() -> None: print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 -@listen() -async def on_startup() -> None: - """Lepton on_startup override.""" - tasks.init() - - def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") diff --git a/jarvis/tasks/__init__.py b/jarvis/tasks/__init__.py deleted file mode 100644 index 9825337..0000000 --- a/jarvis/tasks/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""J.A.R.V.I.S. background task handlers.""" -from jarvis.tasks import twitter, unban, unlock, unwarn - - -def init() -> None: - """Start the background task handlers.""" - unban.unban.start() - unlock.unlock.start() - unwarn.unwarn.start() - twitter.tweets.start() diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py deleted file mode 100644 index eba3a95..0000000 --- a/jarvis/tasks/reminder.py +++ /dev/null @@ -1,45 +0,0 @@ -"""J.A.R.V.I.S. reminder background task handler.""" -from datetime import datetime, timedelta - -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 Reminder - -import jarvis -from jarvis.utils import build_embed - - -@Task.create(trigger=IntervalTrigger(seconds=15)) -async def remind() -> None: - """J.A.R.V.I.S. reminder background task.""" - reminders = Reminder.find(q(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))) - async for reminder in reminders: - if reminder.remind_at <= datetime.utcnow(): - user = await jarvis.jarvis.fetch_user(reminder.user) - if not user: - await reminder.delete() - continue - embed = build_embed( - title="You have a reminder", - description=reminder.message, - fields=[], - ) - embed.set_author( - name=user.username + "#" + user.discriminator, icon_url=user.avatar.url - ) - embed.set_thumbnail(url=user.display_avatar.url) - try: - await user.send(embed=embed) - except Exception: - guild = jarvis.jarvis.fetch_guild(reminder.guild) - channel = guild.get_channel(reminder.channel) if guild else None - if channel and not reminder.private: - await channel.send(f"{user.mention}", embed=embed) - else: - await channel.send( - f"{user.mention}, you had a private reminder set for now, " - "but I couldn't send it to you." - ) - finally: - await reminder.delete() diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py deleted file mode 100644 index 20a8403..0000000 --- a/jarvis/tasks/twitter.py +++ /dev/null @@ -1,63 +0,0 @@ -"""J.A.R.V.I.S. twitter background task handler.""" -import logging -from datetime import datetime, timedelta - -import tweepy -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 Twitter - -import jarvis - -logger = logging.getLogger("jarvis") - - -@Task.create(trigger=IntervalTrigger(minutes=1)) -async def tweets() -> None: - """J.A.R.V.I.S. twitter background task.""" - config = jarvis.config.get_config() - __auth = tweepy.AppAuthHandler( - config.twitter["consumer_key"], jarvis.jconfig.twitter["consumer_secret"] - ) - __api = tweepy.API(__auth) - guild_cache = {} - channel_cache = {} - twitters = Twitter.find(q(active=True)) - async for twitter in twitters: - try: - if not twitter.twitter_id or not twitter.last_sync: - user = __api.get_user(screen_name=twitter.handle) - twitter.update( - q(twitter_id=user.id, handle=user.screen_name, last_sync=datetime.now()) - ) - - if twitter.last_sync + timedelta(hours=1) <= datetime.now(): - user = __api.get_user(id=twitter.twitter_id) - twitter.update(q(handle=user.screen_name, last_sync=datetime.now())) - - if tweets := __api.user_timeline(id=twitter.twitter_id): - guild_id = twitter.guild - channel_id = twitter.channel - tweets = sorted(tweets, key=lambda x: x.id) - if guild_id not in guild_cache: - guild_cache[guild_id] = await jarvis.jarvis.fetch_guild(guild_id) - guild = guild_cache[twitter.guild] - if channel_id not in channel_cache: - channel_cache[channel_id] = await guild.fetch_channel(channel_id) - channel = channel_cache[channel_id] - for tweet in tweets: - retweet = "retweeted_status" in tweet.__dict__ - if retweet and not twitter.retweets: - continue - timestamp = int(tweet.created_at.timestamp()) - url = f"https://twitter.com/{twitter.handle}/status/{tweet.id}" - verb = "re" if retweet else "" - await channel.send( - f"`@{twitter.handle}` {verb}tweeted this at : {url}" - ) - newest = max(tweets, key=lambda x: x.id) - twitter.update(q(last_tweet=newest.id)) - await twitter.commit() - except Exception as e: - logger.error(f"Error with tweets: {e}") diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py deleted file mode 100644 index 55a8409..0000000 --- a/jarvis/tasks/unban.py +++ /dev/null @@ -1,33 +0,0 @@ -"""J.A.R.V.I.S. unban background task handler.""" -from datetime import datetime, timedelta - -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 Ban, Unban - -import jarvis - - -@Task.create(IntervalTrigger(minutes=10)) -async def _unban() -> None: - """J.A.R.V.I.S. unban background task.""" - jarvis_id = jarvis.jconfig.client_id - bans = Ban.find(q(type="temp", active=True)) - async for ban in bans: - if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): - guild = await jarvis.jarvis.fetch_guild(ban.guild) - user = await jarvis.jarvis.fetch_user(ban.user) - if user: - await guild.unban(user=user, reason="Ban expired") - ban.update(q(active=False)) - await ban.commit() - u = Unban( - user=user.id, - guild=guild.id, - username=user.name, - discrim=user.discriminator, - admin=jarvis_id, - reason="Ban expired", - ) - await u.commit() diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py deleted file mode 100644 index 22f6f15..0000000 --- a/jarvis/tasks/unlock.py +++ /dev/null @@ -1,37 +0,0 @@ -"""J.A.R.V.I.S. unlock background task handler.""" -from asyncio import to_thread -from datetime import datetime, timedelta - -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger - -import jarvis -from jarvis.db.models import Lock - - -async def _unlock() -> None: - """J.A.R.V.I.S. unlock blocking task.""" - locks = Lock.objects(active=True) - # Block execution for now - # TODO: Reevaluate with admin/lock[down] - if False: - for lock in locks: - if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow(): - guild = await jarvis.jarvis.fetch_guild(lock.guild) - channel = await guild.fetch_channel(lock.channel) - if channel: - roles = await guild.fetch_roles() - for role in roles: - overrides = channel.overwrites_for(role) - overrides.send_messages = None - await channel.set_permissions( - role, overwrite=overrides, reason="Lock expired" - ) - lock.active = False - lock.save() - - -@Task.create(IntervalTrigger(minutes=1)) -async def unlock() -> None: - """J.A.R.V.I.S. unlock background task.""" - await to_thread(_unlock) diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py deleted file mode 100644 index 84a07db..0000000 --- a/jarvis/tasks/unwarn.py +++ /dev/null @@ -1,17 +0,0 @@ -"""J.A.R.V.I.S. unwarn background task handler.""" -from datetime import datetime, timedelta - -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 Warning - - -@Task.create(IntervalTrigger(hours=1)) -async def unwarn() -> None: - """J.A.R.V.I.S. unwarn background task.""" - warns = Warning.find(q(active=True)) - async for warn in warns: - if warn.created_at + timedelta(hours=warn.duration) < datetime.now(): - warn.update(q(active=False)) - await warn.commit() From 0175fce442cf1ffd6e677a973f4fdd1288c23767 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 20:10:05 -0700 Subject: [PATCH 09/34] Migrate twitter --- jarvis/__init__.py | 6 ++-- jarvis/cogs/twitter.py | 63 ++++++++++++++++++++++++++++-------------- poetry.lock | 4 +-- pyproject.toml | 2 +- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index ebc6693..4b743aa 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -47,8 +47,8 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" - guild = await jarvis.get_guild(DEFAULT_GUILD) - channel = guild.get_channel(DEFAULT_ERROR_CHANNEL) + guild = await jarvis.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"" @@ -104,7 +104,7 @@ async def on_ready() -> None: def run() -> None: """Run J.A.R.V.I.S.""" - connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") + connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") jconfig.get_db_config() for extension in utils.get_extensions(): diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 7d16b92..dc7d875 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -2,8 +2,8 @@ import asyncio import tweepy -from bson import ObjectId from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import get 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 ( @@ -14,7 +14,7 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import check from jarvis_core.db import q -from jarvis_core.db.models import Twitter +from jarvis_core.db.models import TwitterAccount, TwitterFollow from jarvis import jconfig from jarvis.utils.permissions import admin_or_permissions @@ -68,7 +68,7 @@ class TwitterCog(Scale): return try: - account = (await asyncio.to_thread(self.api.get_user(screen_name=handle)))[0] + account = await asyncio.to_thread(self.api.get_user, screen_name=handle) latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0] except Exception: await ctx.send( @@ -76,42 +76,54 @@ class TwitterCog(Scale): ) return - count = len([i async for i in Twitter.find(guild=ctx.guild.id)]) + count = len([i async for i in TwitterFollow.find(q(guild=ctx.guild.id))]) if count >= 12: await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) return - exists = Twitter.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) + exists = await TwitterFollow.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) if exists: await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return - t = Twitter( - handle=account.screen_name, + ta = await TwitterAccount.find_one(q(twitter_id=account.id)) + if not ta: + ta = TwitterAccount( + handle=account.screen_name, + twitter_id=account.id, + last_tweet=latest_tweet.id, + ) + await ta.commit() + + tf = TwitterFollow( twitter_id=account.id, guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, - last_tweet=latest_tweet.id, retweets=retweets, ) - await t.commit() + await tf.commit() await ctx.send(f"Now following `@{handle}` in {channel.mention}") @slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: - twitters = Twitter.objects(guild=ctx.guild.id) + t = TwitterFollow.find(q(guild=ctx.guild.id)) + twitters = [] + async for twitter in t: + twitters.append(twitter) if not twitters: await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] - handlemap = {str(x.id): x.handle for x in twitters} + handlemap = {} for twitter in twitters: - option = SelectOption(label=twitter.handle, value=str(twitter.id)) + account = await TwitterAccount.find_one(q(twitter_id=twitter.twitter_id)) + handlemap[str(twitter.twitter_id)] = account.handle + option = SelectOption(label=account.handle, value=str(twitter.twitter_id)) options.append(option) select = Select( @@ -119,7 +131,7 @@ class TwitterCog(Scale): ) components = [ActionRow(select)] - block = "\n".join(x.handle for x in twitters) + block = "\n".join(x for x in handlemap.values()) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" "Please choose accounts to unfollow", @@ -133,7 +145,8 @@ class TwitterCog(Scale): timeout=60 * 5, ) for to_delete in context.context.values: - _ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete() + follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete)) + await follow.delete() for row in components: for component in row.components: component.disabled = True @@ -164,14 +177,20 @@ class TwitterCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: retweets = retweets == "Yes" - twitters = Twitter.objects(guild=ctx.guild.id) + t = TwitterFollow.find(q(guild=ctx.guild.id)) + twitters = [] + async for twitter in t: + twitters.append(twitter) if not twitters: await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] + handlemap = {} for twitter in twitters: - option = SelectOption(label=twitter.handle, value=str(twitter.id)) + account = await TwitterAccount.find_one(q(twitter_id=twitter.id)) + handlemap[str(twitter.twitter_id)] = account.handle + option = SelectOption(label=account.handle, value=str(twitter.twitter_id)) options.append(option) select = Select( @@ -179,7 +198,7 @@ class TwitterCog(Scale): ) components = [ActionRow(select)] - block = "\n".join(x.handle for x in twitters) + block = "\n".join(x for x in handlemap.values()) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" f"Please choose which accounts to {'un' if not retweets else ''}follow retweets from", @@ -193,11 +212,13 @@ class TwitterCog(Scale): timeout=60 * 5, ) - handlemap = {str(x.id): x.handle for x in twitters} + handlemap = {} for to_update in context.context.values: - t = await Twitter.find_one(q(guild=ctx.guild.id, id=ObjectId(to_update)))() - t.retweets = retweets - t.save() + account = await TwitterAccount.find_one(q(twitter_id=int(to_update))) + handlemap[str(twitter.twitter_id)] = account.handle + t = get(twitters, guild=ctx.guild.id, twitter_id=int(to_update)) + t.update(q(retweets=True)) + await t.commit() for row in components: for component in row.components: diff --git a/poetry.lock b/poetry.lock index af0717b..e90ef29 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.2.1" +version = "0.4.1" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "0e627eae725abb1e6f3766c5dc94bd80d0ac6702" +resolved_reference = "20f67444579d36d508def2b47ea826af9bbdd2d7" [[package]] name = "jedi" diff --git a/pyproject.toml b/pyproject.toml index a897256..98287ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jarvis" -version = "2.0.0a0" +version = "2.0.0a1" description = "J.A.R.V.I.S. admin bot" authors = ["Zevaryx "] From b12b109ad55cbf41c47c53c2cd7e25b50bae7cbd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:26:07 -0700 Subject: [PATCH 10/34] Migrate image cog --- jarvis/cogs/image.py | 104 ++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 27df52e..a9af120 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -5,13 +5,16 @@ from io import BytesIO import aiohttp import cv2 import numpy as np -from dis_snek import MessageContext, Scale, Snake, message_command +from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets - -from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize +from dis_snek.models.discord.message import Attachment +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from jarvis_core.util import build_embed, convert_bytesize, unconvert_bytesize MIN_ACCURACY = 0.80 @@ -26,39 +29,65 @@ class ImageCog(Scale): def __init__(self, bot: Snake): self.bot = bot self._session = aiohttp.ClientSession() - self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE) + self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B?)", re.IGNORECASE) def __del__(self): self._session.close() - async def _resize(self, ctx: MessageContext, target: str, url: str = None) -> None: - if not target: - await ctx.send("Missing target size, i.e. 200KB.") + @slash_command(name="resize", description="Resize an image") + @slash_option( + name="target", + description="Target size, i.e. 200KB", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="attachment", + description="Image to resize", + opt_type=OptionTypes.ATTACHMENT, + required=False, + ) + @slash_option( + name="url", + description="URL to download and resize", + opt_type=OptionTypes.STRING, + required=False, + ) + async def _resize( + self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None + ) -> None: + if not attachment and not url: + await ctx.send("A URL or attachment is required", ephemeral=True) + return + + if attachment and not attachment.content_type.startswith("image"): + await ctx.send("Attachment must be an image", ephemeral=True) return tgt = self.tgt_match.match(target) if not tgt: - await ctx.send(f"Invalid target format ({target}). Expected format like 200KB") + await ctx.send( + f"Invalid target format ({target}). Expected format like 200KB", ephemeral=True + ) return tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) if tgt_size > unconvert_bytesize(8, "MB"): - await ctx.send("Target too large to send. Please make target < 8MB") + await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True) return - file = None - filename = None - if ctx.message.attachments is not None and len(ctx.message.attachments) > 0: - file = await ctx.message.attachments[0].read() - filename = ctx.message.attachments[0].filename - elif url is not None: - async with self._session.get(url) as resp: - if resp.status == 200: - file = await resp.read() - filename = url.split("/")[-1] - else: - ctx.send("Missing file as either attachment or URL.") - size = len(file) + if attachment: + url = attachment.url + filename = attachment.filename + else: + filename = url.split("/")[-1] + + data = None + async with self._session.get(url) as resp: + if resp.status == 200: + data = await resp.read() + + size = len(data) if size <= tgt_size: await ctx.send("Image already meets target.") return @@ -67,43 +96,38 @@ class ImageCog(Scale): accuracy = 0.0 - while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < MIN_ACCURACY): - old_file = file + while len(data) > tgt_size or (len(data) <= tgt_size and accuracy < MIN_ACCURACY): + old_file = data - buffer = np.frombuffer(file, dtype=np.uint8) + buffer = np.frombuffer(data, dtype=np.uint8) img = cv2.imdecode(buffer, flags=-1) width = int(img.shape[1] * ratio) height = int(img.shape[0] * ratio) new_img = cv2.resize(img, (width, height)) - file = cv2.imencode(".png", new_img)[1].tobytes() - accuracy = (len(file) / tgt_size) * 100 + data = cv2.imencode(".png", new_img)[1].tobytes() + accuracy = (len(data) / tgt_size) * 100 if accuracy <= 0.50: - file = old_file + data = old_file ratio += 0.1 else: - ratio = max(tgt_size / len(file) - 0.02, 0.65) + ratio = max(tgt_size / len(data) - 0.02, 0.65) - bufio = BytesIO(file) - accuracy = (len(file) / tgt_size) * 100 + bufio = BytesIO(data) + accuracy = (len(data) / tgt_size) * 100 fields = [ EmbedField("Original Size", convert_bytesize(size), False), - EmbedField("New Size", convert_bytesize(len(file)), False), + EmbedField("New Size", convert_bytesize(len(data)), False), EmbedField("Accuracy", f"{accuracy:.02f}%", False), ] embed = build_embed(title=filename, description="", fields=fields) embed.set_image(url="attachment://resized.png") await ctx.send( embed=embed, - file=File(file=bufio, filename="resized.png"), + file=File(file=bufio, file_name="resized.png"), ) - @message_command(name="resize") - @cooldown(bucket=Buckets.USER, rate=1, interval=60) - async def _resize_pref(self, ctx: MessageContext, target: str, url: str = None) -> None: - await self._resize(ctx, target, url) - def setup(bot: Snake) -> None: """Add ImageCog to J.A.R.V.I.S.""" From 6349321e5cc54c2235ddac420f60bcdce5fe40de Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:26:41 -0700 Subject: [PATCH 11/34] Update remindme to use modals --- jarvis/cogs/remindme.py | 63 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index e7a90b9..ab4aae6 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -5,11 +5,12 @@ from datetime import datetime, timedelta from typing import List from bson import ObjectId -from dis_snek import InteractionContext, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption 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, @@ -20,35 +21,19 @@ from jarvis_core.db import q from jarvis_core.db.models import Reminder from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog -valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") -time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?") +valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>:,\u0080-\U000E0FFF]*") +time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE) invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) -class RemindmeCog(CacheCog): +class RemindmeCog(Scale): """J.A.R.V.I.S. Remind Me Cog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command(name="remindme", description="Set a reminder") - @slash_option( - name="message", - description="What to remind you of?", - opt_type=OptionTypes.STRING, - required=True, - ) - @slash_option( - name="delay", - description="How long? (i.e. 1w 3d 7h 5m 20s)", - opt_type=OptionTypes.STRING, - required=False, - ) @slash_option( name="private", description="Send as DM?", @@ -62,13 +47,35 @@ class RemindmeCog(CacheCog): async def _remindme( self, ctx: InteractionContext, - message: str, - delay: str, private: str = "n", ) -> None: private = private == "y" - if len(message) > 100: - await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True) + modal = Modal( + title="Set your reminder!", + components=[ + InputText( + label="What to remind you?", + placeholder="Reminder", + style=TextStyles.PARAGRAPH, + custom_id="message", + ), + InputText( + label="When to remind you?", + placeholder="1h 30m", + 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) + message = response.responses.get("message") + delay = response.responses.get("delay") + except asyncio.TimeoutError: + return + if len(message) > 500: + await ctx.send("Reminder cannot be > 500 characters.", ephemeral=True) return elif invites.search(message): await ctx.send( @@ -83,7 +90,7 @@ class RemindmeCog(CacheCog): 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, flags=re.I): + if times := time_pattern.findall(delay): for t in times: delta[units[t[-1]]] += float(t[:-1]) else: @@ -96,7 +103,7 @@ class RemindmeCog(CacheCog): await ctx.send("At least one time period is required", ephemeral=True) return - reminders = len(await Reminder.find(q(user=ctx.author.id, active=True))) + 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. " @@ -108,7 +115,7 @@ class RemindmeCog(CacheCog): remind_at = datetime.now() + timedelta(**delta) r = Reminder( - user=ctx.author_id, + user=ctx.author.id, channel=ctx.channel.id, guild=ctx.guild.id, message=message, @@ -138,7 +145,7 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.display_avatar.url) - await ctx.send(embed=embed, ephemeral=private) + await response.send(embed=embed, ephemeral=private) async def get_reminders_embed( self, ctx: InteractionContext, reminders: List[Reminder] From 33440f6150a94babc5c5393542c7a2907c96afae Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:27:55 -0700 Subject: [PATCH 12/34] Move custom Jarvis client class to own file --- jarvis/__init__.py | 84 ++-------------------------------------------- jarvis/client.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 82 deletions(-) create mode 100644 jarvis/client.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 4b743aa..b7c6f1a 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,15 +1,12 @@ """Main J.A.R.V.I.S. package.""" -import json import logging -import traceback -from datetime import datetime -from aiohttp import ClientSession -from dis_snek import Context, Intents, Snake, listen +from dis_snek import Intents from jarvis_core.db import connect # from jarvis import logo # noqa: F401 from jarvis import utils +from jarvis.client import Jarvis from jarvis.config import get_config from jarvis.events import member, message @@ -24,84 +21,12 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS restart_ctx = None -DEFAULT_GUILD = 862402786116763668 -DEFAULT_ERROR_CHANNEL = 943395824560394250 -DEFAULT_URL = "https://paste.zevs.me/documents" - -ERROR_MSG = """ -Command Information: - Name: {invoked_name} - Args: -{arg_str} - -Callback: - Args: -{callback_args} - Kwargs: -{callback_kwargs} -""" - - -class Jarvis(Snake): - async def on_command_error( - self, ctx: Context, error: Exception, *args: list, **kwargs: dict - ) -> None: - """Lepton on_command_error override.""" - guild = await jarvis.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" - ) - 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" - ) - full_message = ERROR_MSG.format( - error_time=error_time, - invoked_name=ctx.invoked_name, - arg_str=arg_str, - callback_args=callback_args, - callback_kwargs=callback_kwargs, - ) - if len(full_message) >= 1900: - error_message = " ".join(traceback.format_exception(error)) - full_message += "Exception: |\n " + error_message - async with ClientSession() as session: - resp = await session.post(DEFAULT_URL, data=full_message) - data = await resp.read() - data = json.loads(data.decode("UTF8")) - - await channel.send( - f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." - f"\nPlease see log at https://paste.zevs.me/{data['key']}" - ) - 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```" - f"\nException:\n```py\n{error_message}\n```" - ) - await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) - return await super().on_command_error(ctx, error, *args, **kwargs) - jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) __version__ = "2.0.0a1" -@listen() -async def on_ready() -> None: - """Lepton on_ready override.""" - global restart_ctx - print(" Logged in as {0.user}".format(jarvis)) # noqa: T001 - print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 - - def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") @@ -110,11 +35,6 @@ def run() -> None: for extension in utils.get_extensions(): jarvis.load_extension(extension) - print( # noqa: T001 - " https://discord.com/api/oauth2/authorize?client_id=" - "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) - ) - jarvis.max_messages = jconfig.max_messages # Add event listeners diff --git a/jarvis/client.py b/jarvis/client.py new file mode 100644 index 0000000..6d5792f --- /dev/null +++ b/jarvis/client.py @@ -0,0 +1,81 @@ +"""Custom JARVIS client.""" +import json +import traceback +from datetime import datetime + +from aiohttp import ClientSession +from dis_snek import Context, Snake, listen + +DEFAULT_GUILD = 862402786116763668 +DEFAULT_ERROR_CHANNEL = 943395824560394250 +DEFAULT_URL = "https://paste.zevs.me/documents" + +ERROR_MSG = """ +Command Information: + Name: {invoked_name} + Args: +{arg_str} + +Callback: + Args: +{callback_args} + Kwargs: +{callback_kwargs} +""" + + +class Jarvis(Snake): + @listen() + async def on_ready(self) -> None: + """Lepton on_ready override.""" + print("Logged in as {}".format(self.user)) # noqa: T001 + print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 + print( # noqa: T001 + "https://discord.com/api/oauth2/authorize?client_id=" + "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) + ) + + async def on_command_error( + self, ctx: Context, error: Exception, *args: list, **kwargs: dict + ) -> None: + """Lepton on_command_error override.""" + 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" + ) + 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" + ) + full_message = ERROR_MSG.format( + error_time=error_time, + invoked_name=ctx.invoked_name, + arg_str=arg_str, + callback_args=callback_args, + callback_kwargs=callback_kwargs, + ) + if len(full_message) >= 1900: + error_message = " ".join(traceback.format_exception(error)) + full_message += "Exception: |\n " + error_message + async with ClientSession() as session: + resp = await session.post(DEFAULT_URL, data=full_message) + data = await resp.read() + data = json.loads(data.decode("UTF8")) + + await channel.send( + f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." + f"\nPlease see log at https://paste.zevs.me/{data['key']}" + ) + 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```" + f"\nException:\n```py\n{error_message}\n```" + ) + await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) + return await super().on_command_error(ctx, error, *args, **kwargs) From a0fdd944325422ed6547a35d47f1885d15c7fea5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:30:25 -0700 Subject: [PATCH 13/34] Add max length to remindme message --- jarvis/cogs/remindme.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index ab4aae6..e53a532 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -58,6 +58,7 @@ class RemindmeCog(Scale): placeholder="Reminder", style=TextStyles.PARAGRAPH, custom_id="message", + max_length=500, ), InputText( label="When to remind you?", From 40fa82df6637940ba5c8ce5d1149f9153ff0a280 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:48:54 -0700 Subject: [PATCH 14/34] Remove owner cog --- jarvis/cogs/owner.py | 46 -------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 jarvis/cogs/owner.py diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py deleted file mode 100644 index 094faf4..0000000 --- a/jarvis/cogs/owner.py +++ /dev/null @@ -1,46 +0,0 @@ -"""J.A.R.V.I.S. Owner Cog.""" -from dis_snek import MessageContext, Scale, Snake, message_command -from dis_snek.models.discord.user import User -from dis_snek.models.snek.checks import is_owner -from dis_snek.models.snek.command import check - -from jarvis import jconfig - - -class OwnerCog(Scale): - """ - J.A.R.V.I.S. management cog. - - Used by admins to control core J.A.R.V.I.S. systems - """ - - def __init__(self, bot: Snake): - self.bot = bot - # self.admins = await Config.find_one(q(key="admins")) - - @message_command(name="addadmin") - @check(is_owner()) - async def _add(self, ctx: MessageContext, user: User) -> None: - if user.id in self.admins.value: - await ctx.send(f"{user.mention} is already an admin.") - return - self.admins.value.append(user.id) - self.admins.save() - jconfig.reload() - await ctx.send(f"{user.mention} is now an admin. Use this power carefully.") - - @message_command(name="deladmin") - @is_owner() - async def _remove(self, ctx: MessageContext, user: User) -> None: - if user.id not in self.admins.value: - await ctx.send(f"{user.mention} is not an admin.") - return - self.admins.value.remove(user.id) - self.admins.save() - jconfig.reload() - await ctx.send(f"{user.mention} is no longer an admin.") - - -def setup(bot: Snake) -> None: - """Add OwnerCog to J.A.R.V.I.S.""" - OwnerCog(bot) From 824548b04dee0021e89002926882ea4d8c32ca7d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 13:04:18 -0700 Subject: [PATCH 15/34] Add on_command listener --- jarvis/__init__.py | 2 +- jarvis/client.py | 39 ++++++++++++++++++++++++++++++++++++++- poetry.lock | 4 ++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index b7c6f1a..45d3232 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -18,7 +18,7 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) logger.addHandler(file_handler) -intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS +intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None diff --git a/jarvis/client.py b/jarvis/client.py index 6d5792f..253abf0 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -4,7 +4,14 @@ import traceback from datetime import datetime from aiohttp import ClientSession -from dis_snek import Context, Snake, listen +from dis_snek import Snake, listen +from dis_snek.models.discord.channel import DMChannel +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.snek.context import Context, InteractionContext +from jarvis_core.db import q +from jarvis_core.db.models import Setting +from jarvis_core.util import build_embed +from jarvis_core.util.ansi import RESET, Fore, Format, fmt DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 @@ -23,6 +30,10 @@ Callback: {callback_kwargs} """ +KEY_FMT = fmt(Fore.GRAY) +VAL_FMT = fmt(Fore.WHITE) +CMD_FMT = fmt(Fore.GREEN, Format.BOLD) + class Jarvis(Snake): @listen() @@ -35,6 +46,32 @@ class Jarvis(Snake): "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) + 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")) + 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()) + fields = [ + EmbedField( + name="Command", + value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", + inline=False, + ), + ] + embed = build_embed( + title="Command Invoked", + description=f"{ctx.author.mention} invoked a command", + fields=fields, + color="#fc9e3f", + ) + embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) + await channel.send(embed=embed) + async def on_command_error( self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: diff --git a/poetry.lock b/poetry.lock index e90ef29..830b51d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.4.1" +version = "0.5.0" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "20f67444579d36d508def2b47ea826af9bbdd2d7" +resolved_reference = "f392757bb773cfeb9b3f14d581a599d13ee6e891" [[package]] name = "jedi" From a72ede0644cb71d4d19df9bdb501d7987e1d61f8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 13:04:47 -0700 Subject: [PATCH 16/34] Remove jokes --- jarvis/cogs/jokes.py | 122 ------------------------------------------- 1 file changed, 122 deletions(-) delete mode 100644 jarvis/cogs/jokes.py diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py deleted file mode 100644 index 7697d37..0000000 --- a/jarvis/cogs/jokes.py +++ /dev/null @@ -1,122 +0,0 @@ -"""J.A.R.V.I.S. Jokes module.""" -import html -import re -import traceback -from datetime import datetime -from random import randint - -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, - slash_option, -) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets -from jarvis_core.db import q -from jarvis_core.db.models import Joke - -from jarvis.utils import build_embed - - -class JokeCog(Scale): - """ - Joke library for J.A.R.V.I.S. - - May adapt over time to create jokes using machine learning - """ - - def __init__(self, bot: Snake): - self.bot = bot - - # TODO: Make this a command group with subcommands - @slash_command( - name="joke", - description="Hear a joke", - ) - @slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER) - @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10) - async def _joke(self, ctx: InteractionContext, id: str = None) -> None: - """Get a joke from the database.""" - try: - if randint(1, 100_000) == 5779 and id is None: # noqa: S311 - await ctx.send(f"<@{ctx.message.author.id}>") - return - # TODO: Add this as a parameter that can be passed in - threshold = 500 # Minimum score - result = None - if id: - result = await Joke.find_one(q(rid=id)) - else: - pipeline = [ - {"$match": {"score": {"$gt": threshold}}}, - {"$sample": {"size": 1}}, - ] - result = Joke.objects().aggregate(pipeline).next() - while result["body"] in ["[removed]", "[deleted]"]: - result = Joke.objects().aggregate(pipeline).next() - - if result is None: - await ctx.send("Humor module failed. Please try again later.", ephemeral=True) - return - emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"]) - for match in emotes: - result["body"] = result["body"].replace(match, html.unescape(match)) - emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["title"]) - for match in emotes: - result["title"] = result["title"].replace(match, html.unescape(match)) - body_chunks = [] - - body = "" - for word in result["body"].split(" "): - if len(body) + 1 + len(word) > 1024: - body_chunks.append(EmbedField("​", body, False)) - body = "" - if word == "\n" and body == "": - continue - elif word == "\n": - body += word - else: - body += " " + word - - desc = "" - title = result["title"] - if len(title) > 256: - new_title = "" - limit = False - for word in title.split(" "): - if len(new_title) + len(word) + 1 > 253 and not limit: - new_title += "..." - desc = "..." - limit = True - if not limit: - new_title += word + " " - else: - desc += word + " " - - body_chunks.append(EmbedField("​", body, False)) - - fields = body_chunks - fields.append(EmbedField("Score", result["score"])) - # Field( - # "Created At", - # str(datetime.fromtimestamp(result["created_utc"])), - # ), - fields.append(EmbedField("ID", result["rid"])) - embed = build_embed( - title=title, - description=desc, - fields=fields, - url=f"https://reddit.com/r/jokes/comments/{result['rid']}", - timestamp=datetime.fromtimestamp(result["created_utc"]), - ) - await ctx.send(embed=embed) - except Exception: - await ctx.send("Encountered error:\n```\n" + traceback.format_exc() + "\n```") - # await ctx.send(f"**{result['title']}**\n\n{result['body']}") - - -def setup(bot: Snake) -> None: - """Add JokeCog to J.A.R.V.I.S.""" - JokeCog(bot) From cbf6587dbfe9143a3fd3883eb3bbb804256a95f1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 14:05:47 -0700 Subject: [PATCH 17/34] Move command to client --- jarvis/cogs/modlog/command.py | 38 ----------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 jarvis/cogs/modlog/command.py diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py deleted file mode 100644 index f846f4c..0000000 --- a/jarvis/cogs/modlog/command.py +++ /dev/null @@ -1,38 +0,0 @@ -"""J.A.R.V.I.S. ModlogCommandCog.""" -from dis_snek import InteractionContext, Snake, listen -from dis_snek.models.discord.channel import DMChannel -from dis_snek.models.discord.embed import EmbedField - -from jarvis.db.models import Setting -from jarvis.utils import build_embed - - -class ModlogCommandCog(object): - """J.A.R.V.I.S. ModlogCommandCog.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_command) - - @listen() - async def on_command(self, ctx: InteractionContext) -> None: - """Process on_slash_command events.""" - if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]: - modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first() - if modlog: - channel = await ctx.guild.get_channel(modlog.value) - args = " ".join(f"{k}:v" for k, v in ctx.kwargs.items()) - fields = [ - EmbedField(name="Command", value=f"{ctx.invoked_name} {args}", inline=False), - ] - embed = build_embed( - title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", - fields=fields, - color="#fc9e3f", - ) - embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) - embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" - ) - await channel.send(embed=embed) From 90506c136efb2e1d816b26392ba6f51e0382cfce Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 14:07:09 -0700 Subject: [PATCH 18/34] Update modlog, member needs full re-work --- jarvis/cogs/modlog/__init__.py | 8 +- jarvis/cogs/modlog/member.py | 333 +-------------------------------- jarvis/cogs/modlog/message.py | 54 +++--- jarvis/cogs/modlog/utils.py | 37 ++-- 4 files changed, 46 insertions(+), 386 deletions(-) diff --git a/jarvis/cogs/modlog/__init__.py b/jarvis/cogs/modlog/__init__.py index 63c09e2..f59f2c3 100644 --- a/jarvis/cogs/modlog/__init__.py +++ b/jarvis/cogs/modlog/__init__.py @@ -1,11 +1,11 @@ """J.A.R.V.I.S. Modlog Cogs.""" -from discord.ext.commands import Bot +from dis_snek import Snake -from jarvis.cogs.modlog import command, member, message +from jarvis.cogs.modlog import command, message -def setup(bot: Bot) -> None: +def setup(bot: Snake) -> None: """Add modlog cogs to J.A.R.V.I.S.""" command.ModlogCommandCog(bot) - member.ModlogMemberCog(bot) + # member.ModlogMemberCog(bot) message.ModlogMessageCog(bot) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py index 99302d7..b00514e 100644 --- a/jarvis/cogs/modlog/member.py +++ b/jarvis/cogs/modlog/member.py @@ -1,332 +1 @@ -"""J.A.R.V.I.S. ModlogMemberCog.""" -import asyncio -from datetime import datetime, timedelta - -import discord -from discord.ext import commands -from discord.utils import find - -from jarvis.cogs.modlog.utils import get_latest_log, modlog_embed -from jarvis.config import get_config -from jarvis.db.models import Ban, Kick, Mute, Setting, Unban -from jarvis.utils import build_embed -from jarvis.utils.field import Field - - -class ModlogMemberCog(commands.Cog): - """J.A.R.V.I.S. ModlogMemberCog.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - self.cache = [] - - @commands.Cog.listener() - async def on_member_ban(self, guild: discord.Guild, user: discord.User) -> None: - """Process on_member_ban events.""" - modlog = Setting.objects(guild=guild.id, setting="modlog").first() - if modlog: - channel = guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await guild.audit_logs( - limit=50, - action=discord.AuditLogAction.ban, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - ban = ( - Ban.objects( - guild=guild.id, - user=user.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if ban: - admin = guild.get_member(ban.admin) - embed = modlog_embed( - user, - admin, - log, - "User banned", - f"{user.mention} was banned from {guild.name}", - ) - - await channel.send(embed=embed) - - @commands.Cog.listener() - async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None: - """Process on_member_unban events.""" - modlog = Setting.objects(guild=guild.id, setting="modlog").first() - if modlog: - channel = guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await guild.audit_logs( - limit=50, - action=discord.AuditLogAction.unban, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - unban = ( - Unban.objects( - guild=guild.id, - user=user.id, - ) - .order_by("-created_at") - .first() - ) - admin = guild.get_member(unban.admin) - embed = modlog_embed( - user, - admin, - log, - "User unbanned", - f"{user.mention} was unbanned from {guild.name}", - ) - - await channel.send(embed=embed) - - @commands.Cog.listener() - async def on_member_remove(self, user: discord.Member) -> None: - """Process on_member_remove events.""" - modlog = Setting.objects(guild=user.guild.id, setting="modlog").first() - if modlog: - channel = user.guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await user.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.kick, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - count = 0 - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - while not log: - if count == 30: - break - await asyncio.sleep(0.5) - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - count += 1 - if not log: - return - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - kick = ( - Kick.objects( - guild=user.guild.id, - user=user.id, - ) - .order_by("-created_at") - .first() - ) - if kick: - admin = user.guild.get_member(kick.admin) - embed = modlog_embed( - user, - admin, - log, - "User Kicked", - f"{user.mention} was kicked from {user.guild.name}", - ) - - await channel.send(embed=embed) - - async def process_mute(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process mute event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - mute = ( - Mute.objects( - guild=before.guild.id, - user=before.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if mute: - admin = before.guild.get_member(mute.admin) - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Muted", - desc=f"{before.mention} was muted", - ) - - async def process_unmute(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process unmute event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - mute = ( - Mute.objects( - guild=before.guild.id, - user=before.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if mute: - admin = before.guild.get_member(mute.admin) - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Muted", - desc=f"{before.mention} was muted", - ) - - async def process_verify(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process verification event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Verified", - desc=f"{before.mention} was verified", - ) - - async def process_rolechange( - self, before: discord.Member, after: discord.Member - ) -> discord.Embed: - """Process rolechange event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - role = None - title = "User Given Role" - verb = "was given" - if len(before.roles) > len(after.roles): - title = "User Forfeited Role" - verb = "forfeited" - role = find(lambda x: x not in after.roles, before.roles) - elif len(before.roles) < len(after.roles): - role = find(lambda x: x not in before.roles, after.roles) - role_text = role.mention if role else "||`[redacted]`||" - return modlog_embed( - member=before, - admin=admin, - log=log, - title=title, - desc=f"{before.mention} {verb} role {role_text}", - ) - - @commands.Cog.listener() - async def on_member_update(self, before: discord.Member, after: discord.Member) -> None: - """Process on_member_update events. - - Caches events due to double-send bug - """ - h = hash(hash(before) * hash(after)) - if h not in self.cache: - self.cache.append(h) - else: - return - modlog = Setting.objects(guild=before.guild.id, setting="modlog").first() - if modlog: - channel = after.guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - embed = None - mute = Setting.objects(guild=before.guild.id, setting="mute").first() - verified = Setting.objects(guild=before.guild.id, setting="verified").first() - mute_role = None - verified_role = None - if mute: - mute_role = before.guild.get_role(mute.value) - if verified: - verified_role = before.guild.get_role(verified.value) - if mute and mute_role in after.roles and mute_role not in before.roles: - embed = await self.process_mute(before, after) - elif mute and mute_role in before.roles and mute_role not in after.roles: - embed = await self.process_unmute(before, after) - elif verified and verified_role not in before.roles and verified_role in after.roles: - embed = await self.process_verify(before, after) - elif before.nick != after.nick: - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - bname = before.nick if before.nick else before.name - aname = after.nick if after.nick else after.name - fields = [ - Field( - name="Before", - value=f"{bname} ({before.name}#{before.discriminator})", - ), - Field( - name="After", - value=f"{aname} ({after.name}#{after.discriminator})", - ), - ] - if log.user.id != before.id: - fields.append( - Field( - name="Moderator", - value=f"{log.user.mention} ({log.user.name}#{log.user.discriminator})", - ) - ) - if log.reason: - fields.append( - Field(name="Reason", value=log.reason, inline=False), - ) - embed = build_embed( - title="User Nick Changed", - description=f"{after.mention} changed their nickname", - color="#fc9e3f", - fields=fields, - timestamp=log.created_at, - ) - embed.set_author(name=f"{after.name}", icon_url=after.display_avatar.url) - embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}") - elif len(before.roles) != len(after.roles): - # TODO: User got a new role - embed = await self.process_rolechange(before, after) - if embed: - await channel.send(embed=embed) - self.cache.remove(h) +"""JARVIS member modlog processing.""" diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py index a00bb69..848a929 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -1,34 +1,37 @@ """J.A.R.V.I.S. ModlogMessageCog.""" -import discord -from discord.ext import commands +from dis_snek import Scale, Snake, listen +from dis_snek.api.events.discord import MessageDelete, MessageUpdate +from dis_snek.models.discord.embed import EmbedField +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 -class ModlogMessageCog(commands.Cog): +class ModlogMessageCog(Scale): """J.A.R.V.I.S. ModlogMessageCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @commands.Cog.listener() - async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None: + @listen() + async def on_message_edit(self, event: MessageUpdate) -> None: """Process on_message_edit events.""" + before = event.before + after = event.after if not before.author.bot: - modlog = Setting.objects(guild=after.guild.id, setting="modlog").first() + modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) if modlog: if before.content == after.content or before.content is None: return channel = before.guild.get_channel(modlog.value) fields = [ - Field( + EmbedField( "Original Message", before.content if before.content else "N/A", False, ), - Field( + EmbedField( "New Message", after.content if after.content else "N/A", False, @@ -39,40 +42,41 @@ class ModlogMessageCog(commands.Cog): description=f"{before.author.mention} edited a message", fields=fields, color="#fc9e3f", - timestamp=after.edited_at, + timestamp=after.edited_timestamp, url=after.jump_url, ) embed.set_author( - name=before.author.name, + name=before.author.username, icon_url=before.author.display_avatar.url, url=after.jump_url, ) embed.set_footer( - text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}" + text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" ) await channel.send(embed=embed) - @commands.Cog.listener() - async def on_message_delete(self, message: discord.Message) -> None: + @listen() + async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" - modlog = Setting.objects(guild=message.guild.id, setting="modlog").first() + message = event.message + modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) if modlog: - fields = [Field("Original Message", message.content or "N/A", False)] + fields = [EmbedField("Original Message", message.content or "N/A", False)] if message.attachments: value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) fields.append( - Field( + EmbedField( name="Attachments", value=value, inline=False, ) ) - if message.stickers: - value = "\n".join([f"[{x.name}]({x.image_url})" for x in message.stickers]) + if message.sticker_items: + value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) fields.append( - Field( + EmbedField( name="Stickers", value=value, inline=False, @@ -82,7 +86,7 @@ class ModlogMessageCog(commands.Cog): if message.embeds: value = str(len(message.embeds)) + " embeds" fields.append( - Field( + EmbedField( name="Embeds", value=value, inline=False, @@ -98,11 +102,11 @@ class ModlogMessageCog(commands.Cog): ) embed.set_author( - name=message.author.name, + name=message.author.username, icon_url=message.author.display_avatar.url, url=message.jump_url, ) embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" ) await channel.send(embed=embed) diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py index 28a63fb..72b5cbe 100644 --- a/jarvis/cogs/modlog/utils.py +++ b/jarvis/cogs/modlog/utils.py @@ -1,31 +1,27 @@ """J.A.R.V.I.S. Modlog Cog Utilities.""" -from datetime import datetime, timedelta -from typing import List - -import discord -from discord import AuditLogEntry, Member -from discord.utils import find +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.guild import AuditLogEntry +from dis_snek.models.discord.user import Member from jarvis.utils import build_embed -from jarvis.utils.field import Field def modlog_embed( - member: discord.Member, - admin: discord.Member, - log: discord.AuditLogEntry, + member: Member, + admin: Member, + log: AuditLogEntry, title: str, desc: str, -) -> discord.Embed: +) -> Embed: """Get modlog embed.""" fields = [ - Field( + EmbedField( name="Moderator", - value=f"{admin.mention} ({admin.name}#{admin.discriminator})", + value=f"{admin.mention} ({admin.username}#{admin.discriminator})", ), ] if log.reason: - fields.append(Field(name="Reason", value=log.reason, inline=False)) + fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) embed = build_embed( title=title, description=desc, @@ -33,15 +29,6 @@ def modlog_embed( fields=fields, timestamp=log.created_at, ) - embed.set_author(name=f"{member.name}", icon_url=member.display_avatar.url) - embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}") + embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) + embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") return embed - - -def get_latest_log(auditlog: List[AuditLogEntry], target: Member) -> AuditLogEntry: - """Filter AuditLog to get latest entry.""" - before = datetime.utcnow() - timedelta(seconds=10) - return find( - lambda x: x.target.id == target.id and x.created_at > before, - auditlog, - ) From 2a84d50c6538ef8ef39551ac7ea87b045211d356 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 15:11:22 -0700 Subject: [PATCH 19/34] Disable lock and lockdown until dis-snek permissions work --- jarvis/cogs/admin/lock.py | 188 ++++++++++++++--------------- jarvis/cogs/admin/mute.py | 242 +++++++++++++++++++------------------- 2 files changed, 215 insertions(+), 215 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 6fe34b7..e17cf58 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. LockCog.""" -from dis_snek import Scale - -# TODO: Uncomment 99% of code once implementation is figured out +# from dis_snek import Scale +# +# # TODO: Uncomment 99% of code once implementation is figured out # from contextlib import suppress # from typing import Union # @@ -20,94 +20,94 @@ from dis_snek import Scale # # 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") +# +# +# 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") diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 13aa948..4be1910 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,122 +1,122 @@ """J.A.R.V.I.S. MuteCog.""" -from datetime import datetime - -from dis_snek import InteractionContext, Permissions, Scale, 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 ( - OptionTypes, - SlashCommandChoice, - slash_command, - slash_option, -) -from dis_snek.models.snek.command import check - -from jarvis.db.models import Mute -from jarvis.utils import build_embed -from jarvis.utils.permissions import admin_or_permissions - - -class MuteCog(Scale): - """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( - name="reason", - description="Reason for mute", - opt_type=OptionTypes.STRING, - required=True, - ) - @slash_option( - name="time", - description="Duration of mute, default 1", - opt_type=OptionTypes.INTEGER, - required=False, - ) - @slash_option( - name="scale", - description="Time scale, default Hour(s)", - opt_type=OptionTypes.INTEGER, - required=False, - choices=[ - SlashCommandChoice(name="Minute(s)", value=1), - SlashCommandChoice(name="Hour(s)", value=60), - SlashCommandChoice(name="Day(s)", value=3600), - SlashCommandChoice(name="Week(s)", value=604800), - ], - ) - @check( - admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS - ) - ) - async def _timeout( - self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 - ) -> None: - if user == ctx.author: - await ctx.send("You cannot mute yourself.", ephemeral=True) - return - if user == self.bot.user: - await ctx.send("I'm afraid I can't let you do that", ephemeral=True) - return - if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", ephemeral=True) - return - - # Max 4 weeks (2419200 seconds) per API - duration = time * scale - if duration > 2419200: - await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) - return - - await user.timeout(communication_disabled_until=duration, reason=reason) - m = Mute( - user=user.id, - reason=reason, - admin=ctx.author.id, - guild=ctx.guild.id, - duration=duration, - active=True, - ) - await m.commit() - - embed = build_embed( - title="User Muted", - description=f"{user.mention} has been muted", - fields=[EmbedField(name="Reason", value=reason)], - ) - 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}") - await ctx.send(embed=embed) - - @slash_command(name="unmute", description="Unmute a user") - @slash_option( - name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True - ) - @check( - admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS - ) - ) - async def _unmute(self, ctx: InteractionContext, user: Member) -> None: - if ( - not user.communication_disabled_until - or user.communication_disabled_until < datetime.now() # noqa: W503 - ): - await ctx.send("User is not muted", ephemeral=True) - return - - embed = build_embed( - title="User Unmuted", - description=f"{user.mention} has been unmuted", - fields=[], - ) - 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}") - await ctx.send(embed=embed) +# from datetime import datetime +# +# from dis_snek import InteractionContext, Permissions, Scale, 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 ( +# OptionTypes, +# SlashCommandChoice, +# slash_command, +# slash_option, +# ) +# from dis_snek.models.snek.command import check +# +# from jarvis.db.models import Mute +# from jarvis.utils import build_embed +# from jarvis.utils.permissions import admin_or_permissions +# +# +# class MuteCog(Scale): +# """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( +# name="reason", +# description="Reason for mute", +# opt_type=OptionTypes.STRING, +# required=True, +# ) +# @slash_option( +# name="time", +# description="Duration of mute, default 1", +# opt_type=OptionTypes.INTEGER, +# required=False, +# ) +# @slash_option( +# name="scale", +# description="Time scale, default Hour(s)", +# opt_type=OptionTypes.INTEGER, +# required=False, +# choices=[ +# SlashCommandChoice(name="Minute(s)", value=1), +# SlashCommandChoice(name="Hour(s)", value=60), +# SlashCommandChoice(name="Day(s)", value=3600), +# SlashCommandChoice(name="Week(s)", value=604800), +# ], +# ) +# @check( +# admin_or_permissions( +# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS +# ) +# ) +# async def _timeout( +# self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 +# ) -> None: +# if user == ctx.author: +# await ctx.send("You cannot mute yourself.", ephemeral=True) +# return +# if user == self.bot.user: +# await ctx.send("I'm afraid I can't let you do that", ephemeral=True) +# return +# if len(reason) > 100: +# await ctx.send("Reason must be < 100 characters", ephemeral=True) +# return +# +# # Max 4 weeks (2419200 seconds) per API +# duration = time * scale +# if duration > 2419200: +# await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) +# return +# +# await user.timeout(communication_disabled_until=duration, reason=reason) +# m = Mute( +# user=user.id, +# reason=reason, +# admin=ctx.author.id, +# guild=ctx.guild.id, +# duration=duration, +# active=True, +# ) +# await m.commit() +# +# embed = build_embed( +# title="User Muted", +# description=f"{user.mention} has been muted", +# fields=[EmbedField(name="Reason", value=reason)], +# ) +# 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}") +# await ctx.send(embed=embed) +# +# @slash_command(name="unmute", description="Unmute a user") +# @slash_option( +# name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True +# ) +# @check( +# admin_or_permissions( +# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS +# ) +# ) +# async def _unmute(self, ctx: InteractionContext, user: Member) -> None: +# if ( +# not user.communication_disabled_until +# or user.communication_disabled_until < datetime.now() # noqa: W503 +# ): +# await ctx.send("User is not muted", ephemeral=True) +# return +# +# embed = build_embed( +# title="User Unmuted", +# description=f"{user.mention} has been unmuted", +# fields=[], +# ) +# 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}") +# await ctx.send(embed=embed) From fa33a79de396329acfc706c989c426d2cb363204 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Feb 2022 11:50:47 -0700 Subject: [PATCH 20/34] Update ban --- jarvis/cogs/admin/ban.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index f1cd3a8..4db8461 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,8 +1,7 @@ """J.A.R.V.I.S. BanCog.""" import re -from datetime import datetime, timedelta -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale 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 @@ -18,16 +17,12 @@ from jarvis_core.db import q from jarvis_core.db.models import Ban, Unban from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog from jarvis.utils.permissions import admin_or_permissions -class BanCog(CacheCog): +class BanCog(Scale): """J.A.R.V.I.S. BanCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - async def discord_apply_ban( self, ctx: InteractionContext, @@ -40,7 +35,7 @@ class BanCog(CacheCog): ) -> None: """Apply a Discord ban.""" await ctx.guild.ban(user, reason=reason) - _ = Ban( + b = Ban( user=user.id, username=user.username, discrim=user.discriminator, @@ -50,7 +45,8 @@ class BanCog(CacheCog): type=mtype, duration=duration, active=active, - ).save() + ) + await b.commit() embed = build_embed( title="User Banned", @@ -70,14 +66,15 @@ class BanCog(CacheCog): async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None: """Apply a Discord unban.""" await ctx.guild.unban(user, reason=reason) - _ = Unban( + u = Unban( user=user.id, username=user.username, discrim=user.discriminator, guild=ctx.guild.id, admin=ctx.author.id, reason=reason, - ).save() + ) + await u.commit() embed = build_embed( title="User Unbanned", @@ -319,10 +316,10 @@ class BanCog(CacheCog): search["active"] = True if btype > 0: search["type"] = types[btype] - bans = Ban.objects(**search).order_by("-created_at") + bans = Ban.find(search).sort([("created_at", -1)]) db_bans = [] fields = [] - for ban in bans: + async 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]" @@ -379,14 +376,4 @@ class BanCog(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, - "btype": btype, - "active": active, - "paginator": paginator, - } - await paginator.send(ctx) From 10fe9384095fddd71bdc724b64f613e8b332d465 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Feb 2022 11:51:43 -0700 Subject: [PATCH 21/34] Use pastypy for logs --- jarvis/client.py | 13 ++++----- poetry.lock | 75 +++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 253abf0..c3ff3da 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,9 +1,7 @@ """Custom JARVIS client.""" -import json import traceback from datetime import datetime -from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField @@ -12,10 +10,11 @@ from jarvis_core.db import q from jarvis_core.db.models import Setting from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt +from pastypy import AsyncPaste as Paste DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 -DEFAULT_URL = "https://paste.zevs.me/documents" +DEFAULT_SITE = "https://paste.zevs.me" ERROR_MSG = """ Command Information: @@ -98,14 +97,12 @@ class Jarvis(Snake): if len(full_message) >= 1900: error_message = " ".join(traceback.format_exception(error)) full_message += "Exception: |\n " + error_message - async with ClientSession() as session: - resp = await session.post(DEFAULT_URL, data=full_message) - data = await resp.read() - data = json.loads(data.decode("UTF8")) + paste = Paste(content=full_message) + await paste.save(DEFAULT_SITE) await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." - f"\nPlease see log at https://paste.zevs.me/{data['key']}" + f"\nPlease see log at {paste.url}" ) else: error_message = "".join(traceback.format_exception(error)) diff --git a/poetry.lock b/poetry.lock index 830b51d..e84ca98 100644 --- a/poetry.lock +++ b/poetry.lock @@ -106,7 +106,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -377,6 +377,29 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "pastypy" +version = "1.0.1" +description = "" +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +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\""} +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\""} +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\""} +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" @@ -436,6 +459,14 @@ 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" +description = "Cryptographic library for Python" +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" @@ -748,7 +779,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "d34963008bb31a5168210290f44909aa684a363455b2d59fe792f41918ec4705" +content-hash = "2adcfd60566d51e43a6f5a3ee0f96140a38e91401800916042cc7cd7e6adb37d" [metadata.files] aiohttp = [ @@ -875,8 +906,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {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.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -1160,6 +1191,10 @@ 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"}, @@ -1247,6 +1282,38 @@ 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"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2"}, + {file = "pycryptodome-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5"}, + {file = "pycryptodome-3.14.1-cp35-abi3-win32.whl", hash = "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192"}, + {file = "pycryptodome-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d"}, + {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"}, diff --git a/pyproject.toml b/pyproject.toml index 98287ba..354687e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ tweepy = "^4.5.0" 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" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 0b3c2e712187710873664c7bd483f90a33f2a71a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Feb 2022 21:53:16 -0700 Subject: [PATCH 22/34] Move modlog and events into jarvis.client --- jarvis/__init__.py | 8 - jarvis/client.py | 363 ++++++++++++++++++++++++++++++--- jarvis/cogs/modlog/__init__.py | 11 - jarvis/cogs/modlog/member.py | 1 - jarvis/cogs/modlog/message.py | 112 ---------- jarvis/cogs/modlog/utils.py | 34 --- jarvis/events/member.py | 23 --- jarvis/events/message.py | 228 --------------------- jarvis/utils/__init__.py | 32 ++- 9 files changed, 367 insertions(+), 445 deletions(-) delete mode 100644 jarvis/cogs/modlog/__init__.py delete mode 100644 jarvis/cogs/modlog/member.py delete mode 100644 jarvis/cogs/modlog/message.py delete mode 100644 jarvis/cogs/modlog/utils.py delete mode 100644 jarvis/events/member.py delete mode 100644 jarvis/events/message.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 45d3232..78628f8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -8,7 +8,6 @@ from jarvis_core.db import connect from jarvis import utils from jarvis.client import Jarvis from jarvis.config import get_config -from jarvis.events import member, message jconfig = get_config() @@ -36,11 +35,4 @@ def run() -> None: jarvis.load_extension(extension) jarvis.max_messages = jconfig.max_messages - - # Add event listeners - if jconfig.events: - _ = [ - member.MemberEventHandler(jarvis), - message.MessageEventHandler(jarvis), - ] jarvis.start(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index c3ff3da..f7e6df3 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,13 +1,19 @@ """Custom JARVIS client.""" +import re import traceback from datetime import datetime from dis_snek import Snake, listen +from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField +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 jarvis_core.db import q -from jarvis_core.db.models import Setting +from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning +from jarvis_core.filters import invites from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste @@ -45,32 +51,6 @@ class Jarvis(Snake): "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) - 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")) - 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()) - fields = [ - EmbedField( - name="Command", - value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", - inline=False, - ), - ] - embed = build_embed( - title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", - fields=fields, - color="#fc9e3f", - ) - embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) - embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" - ) - await channel.send(embed=embed) - async def on_command_error( self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: @@ -113,3 +93,332 @@ class Jarvis(Snake): ) await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) return await super().on_command_error(ctx, error, *args, **kwargs) + + # Modlog + 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")) + 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()) + fields = [ + EmbedField( + name="Command", + value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", + inline=False, + ), + ] + embed = build_embed( + title="Command Invoked", + description=f"{ctx.author.mention} invoked a command", + fields=fields, + color="#fc9e3f", + ) + embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) + await channel.send(embed=embed) + + async def on_message_edit(self, event: MessageUpdate) -> None: + """Process on_message_edit events.""" + before = event.before + after = event.after + if not before.author.bot: + modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) + if modlog: + if 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"{before.author.mention} edited a message", + fields=fields, + color="#fc9e3f", + timestamp=after.edited_timestamp, + url=after.jump_url, + ) + embed.set_author( + name=before.author.username, + icon_url=before.author.display_avatar.url, + url=after.jump_url, + ) + embed.set_footer( + text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" + ) + await channel.send(embed=embed) + if not isinstance(after.channel, DMChannel) and not after.author.bot: + await self.massmention(after) + await self.roleping(after) + await self.checks(after) + await self.roleping(after) + await self.checks(after) + + 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")) + if modlog: + fields = [EmbedField("Original Message", message.content or "N/A", 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, + ) + ) + + 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} | {message.author.id}" + ) + await channel.send(embed=embed) + + # Events + async def on_member_join(self, user: Member) -> None: + """Handle on_member_join event.""" + guild = user.guild + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) + if unverified: + role = guild.get_role(unverified.value) + if role not in user.roles: + await user.add_roles(role, reason="User just joined and is unverified") + + async def autopurge(self, message: Message) -> None: + """Handle autopurge events.""" + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) + if autopurge: + await message.delete(delay=autopurge.delay) + + async def autoreact(self, message: Message) -> None: + """Handle autoreact events.""" + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) + if autoreact: + for reaction in autoreact.reactions: + await message.add_reaction(reaction) + + async def checks(self, message: Message) -> None: + """Other message checks.""" + # #tech + channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) + if channel and message.author.id == 293795462752894976: + await channel.send( + content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 + ) + content = re.sub(r"\s+", "", message.content) + match = invites.search(content) + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) + if not setting: + setting = Setting(guild=message.guild.id, setting="noinvite", value=True) + await setting.commit() + if match: + guild_invites = await message.guild.invites() + allowed = [x.code for x in guild_invites] + [ + "dbrand", + "VtgZntXcnZ", + "gPfYGbvTCE", + ] + if match.group(1) not in allowed and setting.value: + await message.delete() + w = 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() + fields = [ + EmbedField( + name="Reason", + value="Sent an invite link", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 + ) + await message.channel.send(embed=embed) + + async def massmention(self, message: Message) -> None: + """Handle massmention events.""" + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) + if ( + massmention + and massmention.value > 0 # noqa: W503 + and len(message.mentions) # noqa: W503 + - (1 if message.author in message.mentions else 0) # noqa: W503 + > massmention.value # noqa: W503 + ): + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Mass Mention", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def roleping(self, message: Message) -> None: + """Handle roleping events.""" + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) + + if not rolepings: + return + + # Get all role IDs involved with message + roles = [] + for mention in message.role_mentions: + roles.append(mention.id) + for mention in message.mentions: + for role in mention.roles: + roles.append(role.id) + + if not roles: + return + + # Get all roles that are rolepinged + roleping_ids = [r.role for r in rolepings] + + # Get roles in rolepings + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) + + # Check if the user has the role, so they are allowed to ping it + user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) + + # Admins can ping whoever + user_is_admin = message.author.guild_permissions.ADMINISTRATOR + + # Check if user in a bypass list + user_has_bypass = False + for roleping in rolepings: + if message.author.id in roleping.bypass["users"]: + user_has_bypass = True + break + if any(role.id in roleping.bypass["roles"] for role in message.author.roles): + user_has_bypass = True + break + + if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: + w = 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() + fields = [ + EmbedField( + name="Reason", + value="Pinged a blocked role/user with a blocked role", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def on_message(self, event: MessageCreate) -> None: + """Handle on_message event. Calls other event handlers.""" + message = event.message + if not isinstance(message.channel, DMChannel) and not message.author.bot: + await self.autoreact(message) + await self.massmention(message) + await self.roleping(message) + await self.autopurge(message) + await self.checks(message) diff --git a/jarvis/cogs/modlog/__init__.py b/jarvis/cogs/modlog/__init__.py deleted file mode 100644 index f59f2c3..0000000 --- a/jarvis/cogs/modlog/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""J.A.R.V.I.S. Modlog Cogs.""" -from dis_snek import Snake - -from jarvis.cogs.modlog import command, message - - -def setup(bot: Snake) -> None: - """Add modlog cogs to J.A.R.V.I.S.""" - command.ModlogCommandCog(bot) - # member.ModlogMemberCog(bot) - message.ModlogMessageCog(bot) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py deleted file mode 100644 index b00514e..0000000 --- a/jarvis/cogs/modlog/member.py +++ /dev/null @@ -1 +0,0 @@ -"""JARVIS member modlog processing.""" diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py deleted file mode 100644 index 848a929..0000000 --- a/jarvis/cogs/modlog/message.py +++ /dev/null @@ -1,112 +0,0 @@ -"""J.A.R.V.I.S. ModlogMessageCog.""" -from dis_snek import Scale, Snake, listen -from dis_snek.api.events.discord import MessageDelete, MessageUpdate -from dis_snek.models.discord.embed import EmbedField -from jarvis_core.db import q -from jarvis_core.db.models import Setting - -from jarvis.utils import build_embed - - -class ModlogMessageCog(Scale): - """J.A.R.V.I.S. ModlogMessageCog.""" - - def __init__(self, bot: Snake): - self.bot = bot - - @listen() - async def on_message_edit(self, event: MessageUpdate) -> None: - """Process on_message_edit events.""" - before = event.before - after = event.after - if not before.author.bot: - modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) - if modlog: - if 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"{before.author.mention} edited a message", - fields=fields, - color="#fc9e3f", - timestamp=after.edited_timestamp, - url=after.jump_url, - ) - embed.set_author( - name=before.author.username, - icon_url=before.author.display_avatar.url, - url=after.jump_url, - ) - embed.set_footer( - text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" - ) - await channel.send(embed=embed) - - @listen() - 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")) - if modlog: - fields = [EmbedField("Original Message", message.content or "N/A", 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, - ) - ) - - 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} | {message.author.id}" - ) - await channel.send(embed=embed) diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py deleted file mode 100644 index 72b5cbe..0000000 --- a/jarvis/cogs/modlog/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -"""J.A.R.V.I.S. Modlog Cog Utilities.""" -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.guild import AuditLogEntry -from dis_snek.models.discord.user import Member - -from jarvis.utils import build_embed - - -def modlog_embed( - member: Member, - admin: Member, - log: AuditLogEntry, - title: str, - desc: str, -) -> Embed: - """Get modlog embed.""" - fields = [ - EmbedField( - name="Moderator", - value=f"{admin.mention} ({admin.username}#{admin.discriminator})", - ), - ] - if log.reason: - fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) - embed = build_embed( - title=title, - description=desc, - color="#fc9e3f", - fields=fields, - timestamp=log.created_at, - ) - embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) - embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") - return embed diff --git a/jarvis/events/member.py b/jarvis/events/member.py deleted file mode 100644 index f7e4ada..0000000 --- a/jarvis/events/member.py +++ /dev/null @@ -1,23 +0,0 @@ -"""J.A.R.V.I.S. Member event handler.""" -from dis_snek import Snake, listen -from dis_snek.models.discord.user import Member -from jarvis_core.db import q -from jarvis_core.db.models import Setting - - -class MemberEventHandler(object): - """J.A.R.V.I.S. Member event handler.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_member_join) - - @listen() - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild - unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) - if unverified: - role = guild.get_role(unverified.value) - if role not in user.roles: - await user.add_roles(role, reason="User just joined and is unverified") diff --git a/jarvis/events/message.py b/jarvis/events/message.py deleted file mode 100644 index 693bc3c..0000000 --- a/jarvis/events/message.py +++ /dev/null @@ -1,228 +0,0 @@ -"""J.A.R.V.I.S. Message event handler.""" -import re - -from dis_snek import Snake, listen -from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.models.discord.channel import DMChannel -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.message import Message -from jarvis_core.db import q -from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning - -import jarvis -from jarvis.utils import build_embed - -invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 - flags=re.IGNORECASE, -) - - -class MessageEventHandler(object): - """J.A.R.V.I.S. Message event handler.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_message) - self.bot.add_listener(self.on_message_edit) - - async def autopurge(self, message: Message) -> None: - """Handle autopurge events.""" - autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) - if autopurge: - await message.delete(delay=autopurge.delay) - - async def autoreact(self, message: Message) -> None: - """Handle autoreact events.""" - autoreact = await Autoreact.find_one( - q( - guild=message.guild.id, - channel=message.channel.id, - ) - ) - if autoreact: - for reaction in autoreact.reactions: - await message.add_reaction(reaction) - - async def checks(self, message: Message) -> None: - """Other message checks.""" - # #tech - channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) - if channel and message.author.id == 293795462752894976: - await channel.send( - content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 - ) - content = re.sub(r"\s+", "", message.content) - match = invites.search(content) - setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) - if not setting: - setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - await setting.commit() - if match: - guild_invites = await message.guild.invites() - allowed = [x.code for x in guild_invites] + [ - "dbrand", - "VtgZntXcnZ", - "gPfYGbvTCE", - ] - if match.group(1) not in allowed and setting.value: - await message.delete() - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Sent an invite link", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) - await message.channel.send(embed=embed) - - async def massmention(self, message: Message) -> None: - """Handle massmention events.""" - massmention = await Setting.find_one( - q( - guild=message.guild.id, - setting="massmention", - ) - ) - if ( - massmention - and massmention.value > 0 # noqa: W503 - and len(message.mentions) # noqa: W503 - - (1 if message.author in message.mentions else 0) # noqa: W503 - > massmention.value # noqa: W503 - ): - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Mass Mention", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - async def roleping(self, message: Message) -> None: - """Handle roleping events.""" - rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) - - if not rolepings: - return - - # Get all role IDs involved with message - roles = [] - for mention in message.role_mentions: - roles.append(mention.id) - for mention in message.mentions: - for role in mention.roles: - roles.append(role.id) - - if not roles: - return - - # Get all roles that are rolepinged - roleping_ids = [r.role for r in rolepings] - - # Get roles in rolepings - role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) - - # Check if the user has the role, so they are allowed to ping it - user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) - - # Admins can ping whoever - user_is_admin = message.author.guild_permissions.ADMINISTRATOR - - # Check if user in a bypass list - user_has_bypass = False - for roleping in rolepings: - if message.author.id in roleping.bypass["users"]: - user_has_bypass = True - break - if any(role.id in roleping.bypass["roles"] for role in message.author.roles): - user_has_bypass = True - break - - if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Pinged a blocked role/user with a blocked role", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - @listen() - async def on_message(self, message: Message) -> None: - """Handle on_message event. Calls other event handlers.""" - if not isinstance(message.channel, DMChannel) and not message.author.bot: - await self.autoreact(message) - await self.massmention(message) - await self.roleping(message) - await self.autopurge(message) - await self.checks(message) - - @listen() - async def on_message_edit(self, before: Message, after: Message) -> None: - """Handle on_message_edit event. Calls other event handlers.""" - if not isinstance(after.channel, DMChannel) and not after.author.bot: - await self.massmention(after) - await self.roleping(after) - await self.checks(after) - await self.roleping(after) - await self.checks(after) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 009d24b..ad04b08 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -3,7 +3,9 @@ from datetime import datetime from pkgutil import iter_modules import git -from dis_snek.models.discord.embed import Embed +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.guild import AuditLogEntry +from dis_snek.models.discord.user import Member import jarvis.cogs import jarvis.db @@ -35,6 +37,34 @@ def build_embed( return embed +def modlog_embed( + member: Member, + admin: Member, + log: AuditLogEntry, + title: str, + desc: str, +) -> Embed: + """Get modlog embed.""" + fields = [ + EmbedField( + name="Moderator", + value=f"{admin.mention} ({admin.username}#{admin.discriminator})", + ), + ] + if log.reason: + fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) + embed = build_embed( + title=title, + description=desc, + color="#fc9e3f", + fields=fields, + timestamp=log.created_at, + ) + embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) + embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") + return embed + + def get_extensions(path: str = jarvis.cogs.__path__) -> list: """Get J.A.R.V.I.S. cogs.""" config = get_config() From caa94b03126611795ece96c79ba06b326f57bc24 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 8 Mar 2022 07:55:44 -0700 Subject: [PATCH 23/34] Fix get_owner -> fetch_owner --- 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 936c855..aaa8d53 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -219,7 +219,7 @@ class UtilCog(Scale): async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild - owner = await guild.get_owner() + owner = await guild.fetch_owner() owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||" From eba4f1fff0e141dfdb03e2136bd6602e8ce5837b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 10 Mar 2022 22:42:34 -0700 Subject: [PATCH 24/34] Add LICENSE, PRIVACY, TERMS --- .pre-commit-config.yaml | 4 +-- LICENSE | 13 ++++++++++ PRIVACY.md | 55 +++++++++++++++++++++++++++++++++++++++++ TERMS.md | 15 +++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 PRIVACY.md create mode 100644 TERMS.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b88c6fe..045ddd0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: language_version: python3.10 - repo: https://github.com/pre-commit/mirrors-isort - rev: V5.10.1 + rev: v5.10.1 hooks: - id: isort args: ["--profile", "black"] @@ -37,7 +37,7 @@ repos: - id: flake8 additional_dependencies: - flake8-annotations~=2.0 - - flake8-bandit~=2.1 + #- flake8-bandit~=2.1 - flake8-docstrings~=1.5 - flake8-bugbear - flake8-comprehensions diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9a5300f --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +“Commons Clause” License Condition v1.0 + +The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. + +Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. + +For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. + +Software: JARVIS + +License: Expat License + +Licensor: zevaryx diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..9c77c7e --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,55 @@ +# Privacy Policy +Your privacy is important to us. It is JARVIS' policy to respect your privacy and comply with any applicable law and regulation regarding any personal information we may collect about you through our JARVIS bot. + +This policy is effective as of 20 March 2022 and was last updated on 10 March 2022. + +## Information We Collect +Information we collect includes both information you knowingly and actively provide us when using or participating in any of our services and promotions, and any information automatically sent by your devices in the course of accessing our products and services. + +## Log Data +When you use our JARVIS services, if opted in to usage data collection services, we may collect data in certain cicumstances: + +- Administrative activity (i.e. ban, warn, mute) + - Your User ID (either as admin or as the recipient of the activity) + - Guild ID of activity origin + - Your discriminator at time of activity (bans only) + - Your username at time of activity (bans only) +- Admin commands + - User ID of admin who executes admin command +- Reminders + - Your User ID + - The guild in which the command originated + - The channel in which the command originated + - Private text entered via the command +- Starboard + - Message ID of starred message + - Channel ID of starred message + - Guild ID of origin guild +- Automated activity logging + - We store no information about users who edit nicknames, join/leave servers, or other related activities. However, this information, if configured by server admins, is relayed into a Discord channel and is not automatically deleted, nor do we have control over this information. + - This information is also stored by Discord via their Audit Log, which we also have no control over. Please contact Discord for their own privacy policy and asking about your rights on their platform. + +## Use of Information +We use the information we collect to provide, maintain, and improve our services. Common uses where this data may be used includes sending reminders, helping administrate Discord servers, and providing extra utilities into Discord based on user content. + +## Security of Your Personal Information +Although we will do our best to protect the personal information you provide to us, we advise that no method of electronic transmission or storage is 100% secure, and no one can guarantee absolute data security. We will comply with laws applicable to us in respect of any data breach. + +## How Long We Keep Your Personal Information +We keep your personal information only for as long as we need to. This time period may depend on what we are using your information for, in accordance with this privacy policy. If your personal information is no longer required, we will delete it or make it anonymous by removing all details that identify you. + +## Your Rights and Controlling Your Personal Information +You may request access to your personal information, and change what you are okay with us collecting from you. You may also request that we delete your personal identifying information. Please message **zevaryx#5779** on Discord, or join the Discord server at https://discord.gg/4TuFvW5n and ask in there. + +## Limits of Our Policy +Our website may link to external sites that are not operated by us (ie. discord.com). Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices. + +## Changes to This Policy +At our discretion, we may change our privacy policy to reflect updates to our business processes, current acceptable practices, or legislative or regulatory changes. If we decide to change this privacy policy, we will post the changes here at the same link by which you are accessing this privacy policy. + +## Contact Us +For any questions or concerns regarding your privacy, you may contact us using the following details: + +### Discord +#### zevaryx#5779 +#### https://discord.gg/4TuFvW5n diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..4182214 --- /dev/null +++ b/TERMS.md @@ -0,0 +1,15 @@ +# Terms Of Use +Please just be reasonable do not try to mess with the bot in a malicious way. This includes but is not limited to: + +- Spamming +- Flooding +- Hacking +- DOS Attacks + +## Contact Us +For any questions or concerns regarding the terms, feel free to contact us: + +### Discord + +#### zevaryx#5779 +#### https://discord.gg/4TuFvW5n From 06ab5d105fa146d93ed3dc32f5e5a4a98b12caf1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:20:04 -0600 Subject: [PATCH 25/34] Add anti-phishing, ref #111 --- jarvis/client.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index f7e6df3..79f7ffe 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -3,9 +3,12 @@ import re import traceback 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.utils.misc_utils import find, find_all +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import TimeTrigger from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.message import Message @@ -13,7 +16,7 @@ from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis_core.filters import invites +from jarvis_core.filters import invites, url from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste @@ -41,9 +44,34 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): + def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 + super().__init__(*args, **kwargs) + self.phishing_domains = set() + + @Task.create(TimeTrigger()) + async def _update_domains(self) -> None: + 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() + + for update in data: + if update["type"] == "add": + self.phishing_domains.add(update["domain"]) + elif update["type"] == "delete": + self.phishing_domains.discard(update["domain"]) + + async def _sync_domains(self) -> None: + 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 = set(await response.json()) + @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 @@ -413,6 +441,34 @@ class Jarvis(Snake): ) 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.findall(message.content): + if match in self.phishing_domains: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Phishing URL", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Phishing URL", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" message = event.message @@ -422,3 +478,4 @@ class Jarvis(Snake): await self.roleping(message) await self.autopurge(message) await self.checks(message) + await self.phishing(message) From 89907026cdaee832ca894842390f15d1ad36a09b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:36:54 -0600 Subject: [PATCH 26/34] Add unsafe URL checker via spoopy detector --- jarvis/client.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 79f7ffe..e05e958 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -468,6 +468,48 @@ class Jarvis(Snake): text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" ) await message.channel.send(embed=embed) + await message.delete() + + async def malicious_url(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.findall(message.content): + async with ClientSession() as session: + resp = await session.get( + "https://spoopy.oceanlord.me/api/check_website", json={"website": match} + ) + if resp.status != 200: + break + data = await resp.json() + for item in data["processed"]["urls"].values(): + if not item["safe"]: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Unsafe URL", + user=message.author.id, + ) + await w.commit() + reasons = ", ".join(item["not_safe_reasons"]) + fields = [ + EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + break async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" @@ -479,3 +521,4 @@ class Jarvis(Snake): await self.autopurge(message) await self.checks(message) await self.phishing(message) + await self.malicious_url(message) From 60ab06b544727d1628c82435a0f73c1f7339316a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:52:26 -0600 Subject: [PATCH 27/34] Remove phishing sync, rely on external API for now --- jarvis/__init__.py | 4 ++-- jarvis/client.py | 13 ++++++------- poetry.lock | 14 +++++++------- run.py | 4 +++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 78628f8..38c27d8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -26,7 +26,7 @@ jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.s __version__ = "2.0.0a1" -def run() -> None: +async def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") jconfig.get_db_config() @@ -35,4 +35,4 @@ def run() -> None: jarvis.load_extension(extension) jarvis.max_messages = jconfig.max_messages - jarvis.start(jconfig.token) + await jarvis.astart(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index e05e958..921e999 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -7,13 +7,12 @@ from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import TimeTrigger from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField 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 import Task, TimeTrigger from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning from jarvis_core.filters import invites, url @@ -70,8 +69,8 @@ class Jarvis(Snake): @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" - await self._sync_domains() - self._update_domains.start() + # 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 @@ -501,16 +500,17 @@ class Jarvis(Snake): fields=fields, ) embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, + name=message.author.display_name, icon_url=message.author.display_avatar.url, ) embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" ) await message.channel.send(embed=embed) await message.delete() break + @listen() async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" message = event.message @@ -520,5 +520,4 @@ class Jarvis(Snake): await self.roleping(message) await self.autopurge(message) await self.checks(message) - await self.phishing(message) await self.malicious_url(message) diff --git a/poetry.lock b/poetry.lock index e84ca98..2d8bfcf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.5.0" +version = "0.6.0" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "f392757bb773cfeb9b3f14d581a599d13ee6e891" +resolved_reference = "8e3276a81575927e4484dfaa5c3e0e894e3c4707" [[package]] name = "jedi" @@ -555,18 +555,18 @@ test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] [[package]] name = "python-lsp-server" -version = "1.3.3" +version = "1.4.0" description = "Python Language Server for the Language Server Protocol" category = "dev" optional = false -python-versions = ">=3.6" +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 = "*" +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\""} @@ -1444,8 +1444,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.3.3.tar.gz", hash = "sha256:1b48ccd8b70103522e8a8b9cb9ae1be2b27a5db0dfd661e7e44e6253ebefdc40"}, - {file = "python_lsp_server-1.3.3-py3-none-any.whl", hash = "sha256:ea7b1e623591ccbfbbf8e5dfe0df8119f27863125a58bdcacbacd1937d8e8cb3"}, + {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"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, diff --git a/run.py b/run.py index 7e6a73e..d53ee79 100755 --- a/run.py +++ b/run.py @@ -1,5 +1,7 @@ """Main run file for J.A.R.V.I.S.""" +import asyncio + from jarvis import run if __name__ == "__main__": - run() + asyncio.run(run()) From 526038c725122243d3e084445af5e4dd6d321ceb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 00:32:45 -0600 Subject: [PATCH 28/34] Re-add phishing sync, fix all client events, dual layer phishing check --- jarvis/client.py | 597 ++++++++++++++++++++++++----------------------- 1 file changed, 307 insertions(+), 290 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 921e999..5e75498 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -6,13 +6,15 @@ 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.utils.misc_utils import find, find_all +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 import Task, TimeTrigger +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 Autopurge, Autoreact, Roleping, Setting, Warning from jarvis_core.filters import invites, url @@ -45,9 +47,9 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 super().__init__(*args, **kwargs) - self.phishing_domains = set() + self.phishing_domains = [] - @Task.create(TimeTrigger()) + @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/recent/86415") @@ -56,21 +58,23 @@ class Jarvis(Snake): for update in data: if update["type"] == "add": - self.phishing_domains.add(update["domain"]) + if update["domain"] not in self.phishing_domains: + self.phishing_domains.append(update["domain"]) elif update["type"] == "delete": - self.phishing_domains.discard(update["domain"]) + if update["domain"] in self.phishing_domains: + self.phishing_domains.remove(update["domain"]) async def _sync_domains(self) -> None: 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 = set(await response.json()) + self.phishing_domains = await response.json() @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" - # await self._sync_domains() - # self._update_domains.start() + 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 @@ -144,18 +148,301 @@ class Jarvis(Snake): ) embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}" ) await channel.send(embed=embed) + # Events + async def on_member_join(self, user: Member) -> None: + """Handle on_member_join event.""" + guild = user.guild + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) + if unverified: + role = guild.get_role(unverified.value) + if role not in user.roles: + await user.add_role(role, reason="User just joined and is unverified") + + async def autopurge(self, message: Message) -> None: + """Handle autopurge events.""" + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) + if autopurge: + await message.delete(delay=autopurge.delay) + + async def autoreact(self, message: Message) -> None: + """Handle autoreact events.""" + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) + if autoreact: + for reaction in autoreact.reactions: + await message.add_reaction(reaction) + + async def checks(self, message: Message) -> None: + """Other message checks.""" + # #tech + # channel = find(lambda x: x.id == 599068193339736096, message._mention_ids) + # if channel and message.author.id == 293795462752894976: + # await channel.send( + # content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 + # ) + content = re.sub(r"\s+", "", message.content) + match = invites.search(content) + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) + if not setting: + 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) + allowed = [x.code for x in guild_invites] + [ + "dbrand", + "VtgZntXcnZ", + "gPfYGbvTCE", + ] + if match.group(1) not in allowed and setting.value: + await message.delete() + w = 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() + fields = [ + EmbedField( + name="Reason", + value="Sent an invite link", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" # noqa: E501 + ) + await message.channel.send(embed=embed) + + async def massmention(self, message: Message) -> None: + """Handle massmention events.""" + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) + + if ( + massmention + and massmention.value > 0 # noqa: W503 + and len(message._mention_ids + message._mention_roles) # noqa: W503 + - (1 if message.author in message._mention_ids else 0) # noqa: W503 + > massmention.value # noqa: W503 + ): + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Mass Mention", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def roleping(self, message: Message) -> None: + """Handle roleping events.""" + if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: + return + rolepings = Roleping.find(q(guild=message.guild.id, active=True)) + + # Get all role IDs involved with message + roles = [] + async for mention in message.mention_roles: + roles.append(mention.id) + async for mention in message.mention_users: + for role in mention.roles: + roles.append(role.id) + + if not roles: + return + + # Get all roles that are rolepinged + roleping_ids = [r.role for r in rolepings] + + # Get roles in rolepings + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) + + # Check if the user has the role, so they are allowed to ping it + user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) + + # Admins can ping whoever + user_is_admin = message.author.has_permission(Permissions.ADMINISTRATOR) + + # Check if user in a bypass list + user_has_bypass = False + async for roleping in rolepings: + if message.author.id in roleping.bypass["users"]: + user_has_bypass = True + break + if any(role.id in roleping.bypass["roles"] for role in message.author.roles): + user_has_bypass = True + break + + if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: + w = 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() + fields = [ + EmbedField( + name="Reason", + value="Pinged a blocked role/user with a blocked role", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + 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( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Phishing URL", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField(name="Reason", value="Phishing URL", inline=False), + EmbedField(name="Message ID", value=str(message.id)), + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + return True + return False + + async def malicious_url(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.finditer(message.content): + async with ClientSession() as session: + resp = await session.get( + "https://spoopy.oceanlord.me/api/check_website", json={"website": match.string} + ) + if resp.status != 200: + break + data = await resp.json() + for item in data["processed"]["urls"].values(): + if not item["safe"]: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Unsafe URL", + user=message.author.id, + ) + await w.commit() + reasons = ", ".join(item["not_safe_reasons"]) + fields = [ + EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False), + EmbedField(name="Message ID", valud=str(message.id)), + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + return True + return False + + @listen() + async def on_message(self, event: MessageCreate) -> None: + """Handle on_message event. Calls other event handlers.""" + message = event.message + if not isinstance(message.channel, DMChannel) and not message.author.bot: + await self.autoreact(message) + await self.massmention(message) + await self.roleping(message) + await self.autopurge(message) + await self.checks(message) + if not await self.phishing(message): + await self.malicious_url(message) + + @listen() async def on_message_edit(self, event: MessageUpdate) -> None: """Process on_message_edit events.""" before = event.before after = event.after - if not before.author.bot: + if not after.author.bot: modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) if modlog: - if before.content == after.content or before.content is None: + if not before or before.content == after.content or before.content is None: return channel = before.guild.get_channel(modlog.value) fields = [ @@ -172,19 +459,19 @@ class Jarvis(Snake): ] embed = build_embed( title="Message Edited", - description=f"{before.author.mention} edited a message", + description=f"{after.author.mention} edited a message", fields=fields, color="#fc9e3f", timestamp=after.edited_timestamp, url=after.jump_url, ) embed.set_author( - name=before.author.username, - icon_url=before.author.display_avatar.url, + name=after.author.username, + icon_url=after.author.display_avatar.url, url=after.jump_url, ) embed.set_footer( - text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" + text=f"{after.author.user.username}#{after.author.discriminator} | {after.author.id}" ) await channel.send(embed=embed) if not isinstance(after.channel, DMChannel) and not after.author.bot: @@ -193,7 +480,10 @@ class Jarvis(Snake): await self.checks(after) await self.roleping(after) await self.checks(after) + if not await self.phishing(after): + await self.malicious_url(after) + @listen() async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" message = event.message @@ -245,279 +535,6 @@ class Jarvis(Snake): url=message.jump_url, ) embed.set_footer( - text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" ) await channel.send(embed=embed) - - # Events - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild - unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) - if unverified: - role = guild.get_role(unverified.value) - if role not in user.roles: - await user.add_roles(role, reason="User just joined and is unverified") - - async def autopurge(self, message: Message) -> None: - """Handle autopurge events.""" - autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) - if autopurge: - await message.delete(delay=autopurge.delay) - - async def autoreact(self, message: Message) -> None: - """Handle autoreact events.""" - autoreact = await Autoreact.find_one( - q( - guild=message.guild.id, - channel=message.channel.id, - ) - ) - if autoreact: - for reaction in autoreact.reactions: - await message.add_reaction(reaction) - - async def checks(self, message: Message) -> None: - """Other message checks.""" - # #tech - channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) - if channel and message.author.id == 293795462752894976: - await channel.send( - content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 - ) - content = re.sub(r"\s+", "", message.content) - match = invites.search(content) - setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) - if not setting: - setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - await setting.commit() - if match: - guild_invites = await message.guild.invites() - allowed = [x.code for x in guild_invites] + [ - "dbrand", - "VtgZntXcnZ", - "gPfYGbvTCE", - ] - if match.group(1) not in allowed and setting.value: - await message.delete() - w = 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() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) - await message.channel.send(embed=embed) - - async def massmention(self, message: Message) -> None: - """Handle massmention events.""" - massmention = await Setting.find_one( - q( - guild=message.guild.id, - setting="massmention", - ) - ) - if ( - massmention - and massmention.value > 0 # noqa: W503 - and len(message.mentions) # noqa: W503 - - (1 if message.author in message.mentions else 0) # noqa: W503 - > massmention.value # noqa: W503 - ): - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Mass Mention", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - async def roleping(self, message: Message) -> None: - """Handle roleping events.""" - rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) - - if not rolepings: - return - - # Get all role IDs involved with message - roles = [] - for mention in message.role_mentions: - roles.append(mention.id) - for mention in message.mentions: - for role in mention.roles: - roles.append(role.id) - - if not roles: - return - - # Get all roles that are rolepinged - roleping_ids = [r.role for r in rolepings] - - # Get roles in rolepings - role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) - - # Check if the user has the role, so they are allowed to ping it - user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) - - # Admins can ping whoever - user_is_admin = message.author.guild_permissions.ADMINISTRATOR - - # Check if user in a bypass list - user_has_bypass = False - for roleping in rolepings: - if message.author.id in roleping.bypass["users"]: - user_has_bypass = True - break - if any(role.id in roleping.bypass["roles"] for role in message.author.roles): - user_has_bypass = True - break - - if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = 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() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - 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.findall(message.content): - if match in self.phishing_domains: - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Phishing URL", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Phishing URL", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - await message.delete() - - async def malicious_url(self, message: Message) -> None: - """Check if the message contains any known phishing domains.""" - for match in url.findall(message.content): - async with ClientSession() as session: - resp = await session.get( - "https://spoopy.oceanlord.me/api/check_website", json={"website": match} - ) - if resp.status != 200: - break - data = await resp.json() - for item in data["processed"]["urls"].values(): - if not item["safe"]: - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Unsafe URL", - user=message.author.id, - ) - await w.commit() - reasons = ", ".join(item["not_safe_reasons"]) - fields = [ - EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - await message.delete() - break - - @listen() - async def on_message(self, event: MessageCreate) -> None: - """Handle on_message event. Calls other event handlers.""" - message = event.message - if not isinstance(message.channel, DMChannel) and not message.author.bot: - await self.autoreact(message) - await self.massmention(message) - await self.roleping(message) - await self.autopurge(message) - await self.checks(message) - await self.malicious_url(message) From 51c3298b867354c3388ea33e2c1b61fc83dcf330 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:26:24 -0600 Subject: [PATCH 29/34] Create jarvis.utils.embeds for various embeds --- jarvis/client.py | 96 +++++------------------------------------- jarvis/utils/embeds.py | 21 +++++++++ 2 files changed, 31 insertions(+), 86 deletions(-) create mode 100644 jarvis/utils/embeds.py diff --git a/jarvis/client.py b/jarvis/client.py index 5e75498..80c1c9d 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -22,6 +22,8 @@ from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste +from jarvis.utils.embeds import warning_embed + DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 DEFAULT_SITE = "https://paste.zevs.me" @@ -213,25 +215,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) + embed = warning_embed(message.author, "Sent an invite link") await message.channel.send(embed=embed) async def massmention(self, message: Message) -> None: @@ -247,7 +231,7 @@ class Jarvis(Snake): massmention and massmention.value > 0 # noqa: W503 and len(message._mention_ids + message._mention_roles) # noqa: W503 - - (1 if message.author in message._mention_ids else 0) # noqa: W503 + - (1 if message.author.id in message._mention_ids else 0) # noqa: W503 > massmention.value # noqa: W503 ): w = Warning( @@ -259,26 +243,14 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, "Mass Mention") await message.channel.send(embed=embed) async def roleping(self, message: Message) -> None: """Handle roleping events.""" if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: return - rolepings = Roleping.find(q(guild=message.guild.id, active=True)) + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) # Get all role IDs involved with message roles = [] @@ -305,7 +277,7 @@ class Jarvis(Snake): # Check if user in a bypass list user_has_bypass = False - async for roleping in rolepings: + for roleping in rolepings: if message.author.id in roleping.bypass["users"]: user_has_bypass = True break @@ -323,25 +295,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + 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: @@ -357,22 +311,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField(name="Reason", value="Phishing URL", inline=False), - EmbedField(name="Message ID", value=str(message.id)), - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, "Phishing URL") await message.channel.send(embed=embed) await message.delete() return True @@ -400,22 +339,7 @@ class Jarvis(Snake): ) await w.commit() reasons = ", ".join(item["not_safe_reasons"]) - fields = [ - EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False), - EmbedField(name="Message ID", valud=str(message.id)), - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, reasons) await message.channel.send(embed=embed) await message.delete() return True diff --git a/jarvis/utils/embeds.py b/jarvis/utils/embeds.py new file mode 100644 index 0000000..31c9fbc --- /dev/null +++ b/jarvis/utils/embeds.py @@ -0,0 +1,21 @@ +"""JARVIS bot-specific embeds.""" +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.user import Member +from jarvis_core.util import build_embed + + +def warning_embed(user: Member, reason: str) -> Embed: + """ + Generate a warning embed. + + Args: + user: User to warn + reason: Warning reason + """ + fields = [EmbedField(name="Reason", value=reason, inline=False)] + embed = build_embed( + title="Warning", description=f"{user.mention} has been warned", fields=fields + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + return embed From bf991aa6003d1e8cfed450f49eb51508c206b14e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:27:04 -0600 Subject: [PATCH 30/34] Fix add star. TODO: make sure starboard works --- jarvis/cogs/starboard.py | 104 +++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index a70d03a..8ae8da8 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -36,7 +36,7 @@ class StarboardCog(Scale): @slash_command(name="starboard", 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 = Starboard.objects(guild=ctx.guild.id) + starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) if starboards != []: message = "Available Starboards:\n" for s in starboards: @@ -71,16 +71,16 @@ class StarboardCog(Scale): await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True) return - count = Starboard.objects(guild=ctx.guild.id).count() + count = await Starboard.count_documents(q(guild=ctx.guild.id)) if count >= 25: await ctx.send("25 starboard limit reached", ephemeral=True) return - _ = Starboard( + await Starboard( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, - ).save() + ).commit() await ctx.send(f"Starboard created. Check it out at {channel.mention}.") @slash_command( @@ -94,29 +94,13 @@ class StarboardCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None: - deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete() - if deleted: - _ = Star.objects(starboard=channel.id).delete() + found = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id)) + if found: + await found.delete() await ctx.send(f"Starboard deleted from {channel.mention}.") else: await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True) - @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) - async def _star_message(self, ctx: InteractionContext) -> None: - await self._star_add._can_run(ctx) - await self._star_add.callback(ctx, message=str(ctx.target_id)) - - @slash_command(name="star", sub_cmd_name="add", description="Star a message") - @slash_option( - name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True - ) - @slash_option( - name="channel", - description="Channel that has the message, not required if used in same channel", - opt_type=OptionTypes.CHANNEL, - required=False, - ) - @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _star_add( self, ctx: InteractionContext, @@ -125,7 +109,7 @@ class StarboardCog(Scale): ) -> None: if not channel: channel = ctx.channel - starboards = Starboard.objects(guild=ctx.guild.id) + starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) if not starboards: await ctx.send("No starboards exist.", ephemeral=True) return @@ -135,7 +119,7 @@ class StarboardCog(Scale): if not isinstance(message, Message): if message.startswith("https://"): message = message.split("/")[-1] - message = await channel.get_message(int(message)) + message = await channel.fetch_message(int(message)) if not message: await ctx.send("Message not found", ephemeral=True) @@ -167,12 +151,14 @@ class StarboardCog(Scale): starboard = channel_list[int(com_ctx.context.values[0])] - exists = Star.objects( - message=message.id, - channel=message.channel.id, - guild=message.guild.id, - starboard=starboard.id, - ).first() + exists = await Star.find_one( + q( + message=message.id, + channel=channel.id, + guild=ctx.guild.id, + starboard=starboard.id, + ) + ) if exists: await ctx.send( @@ -181,7 +167,7 @@ class StarboardCog(Scale): ) return - count = Star.objects(guild=message.guild.id, starboard=starboard.id).count() + count = await Star.count_documents(q(guild=ctx.guild.id, starboard=starboard.id)) content = message.content attachments = message.attachments @@ -204,24 +190,24 @@ class StarboardCog(Scale): embed.set_author( name=message.author.display_name, url=message.jump_url, - icon_url=message.author.display_avatar.url, + icon_url=message.author.avatar.url, ) - embed.set_footer(text=message.guild.name + " | " + message.channel.name) + embed.set_footer(text=ctx.guild.name + " | " + channel.name) if image_url: embed.set_image(url=image_url) star = await starboard.send(embed=embed) - _ = Star( + await Star( index=count, message=message.id, - channel=message.channel.id, - guild=message.guild.id, + channel=channel.id, + guild=ctx.guild.id, starboard=starboard.id, admin=ctx.author.id, star=star.id, active=True, - ).save() + ).commit() components[0].components[0].disabled = True @@ -230,6 +216,27 @@ class StarboardCog(Scale): components=components, ) + @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + 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_option( + name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="channel", + description="Channel that has the message, not required if used in same channel", + opt_type=OptionTypes.CHANNEL, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _star_message_slash( + self, ctx: InteractionContext, message: str, channel: GuildText + ) -> None: + await self._star_add(ctx, message, channel) + @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True @@ -250,30 +257,33 @@ class StarboardCog(Scale): if not isinstance(starboard, GuildText): await ctx.send("Channel must be a GuildText channel", ephemeral=True) return + exists = await Starboard.find_one(q(channel=starboard.id, guild=ctx.guild.id)) if not exists: + # TODO: automagically create starboard await ctx.send( f"Starboard does not exist in {starboard.mention}. Please create it first", ephemeral=True, ) return - star = Star.objects( - starboard=starboard.id, - index=id, - guild=ctx.guild.id, - active=True, - ).first() + star = await Star.find_one( + q( + starboard=starboard.id, + index=id, + guild=ctx.guild.id, + active=True, + ) + ) if not star: await ctx.send(f"No star exists with id {id}", ephemeral=True) return - message = await starboard.get_message(star.star) + message = await starboard.fetch_message(star.star) if message: await message.delete() - star.active = False - star.save() + await star.delete() await ctx.send(f"Star {id} deleted from {starboard.mention}") From 3921c41f1588217068ee39684076cd33ffc13fa0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:27:48 -0600 Subject: [PATCH 31/34] Remove MongoEngine-related database stuff --- jarvis/db/models.py | 264 --------------------------------------- jarvis/utils/__init__.py | 1 - 2 files changed, 265 deletions(-) delete mode 100644 jarvis/db/models.py diff --git a/jarvis/db/models.py b/jarvis/db/models.py deleted file mode 100644 index 5c9b145..0000000 --- a/jarvis/db/models.py +++ /dev/null @@ -1,264 +0,0 @@ -"""J.A.R.V.I.S. database object for mongoengine.""" -from datetime import datetime - -from mongoengine import Document -from mongoengine.fields import ( - BooleanField, - DateTimeField, - DictField, - DynamicField, - IntField, - ListField, - LongField, - StringField, -) - - -class SnowflakeField(LongField): - """Snowflake LongField Override.""" - - pass - - -class Autopurge(Document): - """Autopurge database object.""" - - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - delay = IntField(min_value=1, max_value=300, default=30) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Autoreact(Document): - """Autoreact database object.""" - - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - reactions = ListField(field=StringField()) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Ban(Document): - """Ban database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - user = SnowflakeField(required=True) - username = StringField(required=True) - discrim = IntField(min_value=1, max_value=9999, required=True) - duration = IntField(min_value=1, max_value=744, required=False) - guild = SnowflakeField(required=True) - type = StringField(default="perm", max_length=4, required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Config(Document): - """Config database object.""" - - key = StringField(required=True) - value = DynamicField(required=True) - - meta = {"db_alias": "main"} - - -class Guess(Document): - """Guess database object.""" - - correct = BooleanField(default=False) - guess = StringField(max_length=800, required=True) - user = SnowflakeField(required=True) - - meta = {"db_alias": "ctc2"} - - -class Joke(Document): - """Joke database object.""" - - rid = StringField() - body = StringField() - title = StringField() - created_utc = DateTimeField() - over_18 = BooleanField() - score = IntField() - - meta = {"db_alias": "main"} - - -class Kick(Document): - """Kick database object.""" - - admin = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - user = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Lock(Document): - """Lock database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - duration = IntField(min_value=1, max_value=300, default=10) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Mute(Document): - """Mute database object.""" - - active = BooleanField(default=True) - user = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - duration = IntField(min_value=-1, max_value=300, default=10) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Purge(Document): - """Purge database object.""" - - admin = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - count = IntField(min_value=1, default=10) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Reminder(Document): - """Reminder database object.""" - - active = BooleanField(default=True) - user = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - message = StringField(max_length=100, required=True) - remind_at = DateTimeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - private = BooleanField(default=False) - - meta = {"db_alias": "main"} - - -class Rolegiver(Document): - """Rolegiver database object.""" - - guild = SnowflakeField(required=True) - roles = ListField(field=SnowflakeField()) - - meta = {"db_alias": "main"} - - -class Roleping(Document): - """Roleping database object.""" - - active = BooleanField(default=True) - role = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - bypass = DictField() - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Setting(Document): - """Setting database object.""" - - guild = SnowflakeField(required=True) - setting = StringField(required=True) - value = DynamicField() - - meta = {"db_alias": "main"} - - -class Star(Document): - """Star database object.""" - - active = BooleanField(default=True) - index = IntField(required=True) - message = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - starboard = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - star = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Starboard(Document): - """Starboard database object.""" - - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Twitter(Document): - """Twitter Follow object.""" - - active = BooleanField(default=True) - twitter_id = IntField(required=True) - handle = StringField(required=True) - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - last_tweet = SnowflakeField(required=True) - retweets = BooleanField(default=True) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - last_sync = DateTimeField() - - meta = {"db_alias": "main"} - - -class Unban(Document): - """Unban database object.""" - - user = SnowflakeField(required=True) - username = StringField(required=True) - discrim = IntField(min_value=1, max_value=9999, required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Warning(Document): - """Warning database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - user = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - duration = IntField(min_value=1, max_value=120, default=24) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index ad04b08..6b0d89a 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -8,7 +8,6 @@ from dis_snek.models.discord.guild import AuditLogEntry from dis_snek.models.discord.user import Member import jarvis.cogs -import jarvis.db from jarvis.config import get_config __all__ = ["cachecog", "permissions"] From a9d736c7a81a193d8a389c4549b267caacf162c5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:29:04 -0600 Subject: [PATCH 32/34] Fix admin-related commands. TODO: Settings, Lock, Lockdown, Mute --- jarvis/cogs/admin/ban.py | 8 --- jarvis/cogs/admin/purge.py | 10 ++- jarvis/cogs/admin/roleping.py | 122 +++++++++++++++++----------------- jarvis/cogs/admin/warning.py | 54 ++++++--------- 4 files changed, 87 insertions(+), 107 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 4db8461..f4742cb 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -302,14 +302,6 @@ class BanCog(Scale): @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, btype=btype, active=active) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return types = [0, "perm", "temp", "soft"] search = {"guild": ctx.guild.id} if active: diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index a5a73c6..91d7caa 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -37,13 +37,12 @@ class PurgeCog(Scale): async for message in ctx.channel.history(limit=amount + 1): messages.append(message) await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") - p = Purge( + await Purge( channel=ctx.channel.id, guild=ctx.guild.id, admin=ctx.author.id, count=amount, - ) - await p.commit() + ).commit() @slash_command( name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages" @@ -79,13 +78,12 @@ class PurgeCog(Scale): await ctx.send("Autopurge already exists.", ephemeral=True) return - p = Autopurge( + await Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, - ) - await p.commit() + ).commit() await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 57e40dc..a9bf020 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -32,13 +32,13 @@ class RolepingCog(Scale): await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return - _ = Roleping( + _ = await Roleping( role=role.id, guild=ctx.guild.id, admin=ctx.author.id, active=True, bypass={"roles": [], "users": []}, - ).save() + ).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") @@ -47,29 +47,29 @@ class RolepingCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=role.id) + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: await ctx.send("Roleping does not exist", ephemeral=True) return - roleping.delete() + 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") async def _roleping_list(self, ctx: InteractionContext) -> None: - rolepings = Roleping.objects(guild=ctx.guild.id) + rolepings = await Roleping.find(q(guild=ctx.guild.id)).to_list(None) if not rolepings: await ctx.send("No rolepings configured", ephemeral=True) return embeds = [] for roleping in rolepings: - role = await ctx.guild.get_role(roleping.role) + role = await ctx.guild.fetch_role(roleping.role) 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 = [ - await ctx.guild.get_member(u).mention or "||`[redacted]`||" + (await ctx.guild.fetch_member(u)).mention or "||`[redacted]`||" for u in roleping.bypass["users"] ] bypass_roles = bypass_roles or ["None"] @@ -84,24 +84,26 @@ class RolepingCog(Scale): value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"), inline=False, ), - EmbedField(name="Active", value=str(roleping.active)), + # EmbedField(name="Active", value=str(roleping.active), inline=True), EmbedField( name="Bypass Users", value="\n".join(bypass_users), + inline=True, ), EmbedField( name="Bypass Roles", value="\n".join(bypass_roles), + inline=True, ), ], ) - admin = await ctx.guild.get_member(roleping.admin) + admin = await ctx.guild.fetch_member(roleping.admin) if not admin: admin = self.bot.user embed.set_author(name=admin.display_name, icon_url=admin.display_avatar.url) - embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}") + embed.set_footer(text=f"{admin.username}#{admin.discriminator} | {admin.id}") embeds.append(embed) @@ -117,21 +119,23 @@ class RolepingCog(Scale): sub_cmd_name="user", sub_cmd_description="Add a user as a bypass to a roleping", ) - @slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="User to add", opt_type=OptionTypes.USER, required=True + ) + @slash_option( + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_bypass_user( - self, ctx: InteractionContext, user: Member, rping: Role + self, ctx: InteractionContext, bypass: Member, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if user.id in roleping.bypass["users"]: - await ctx.send(f"{user.mention} already in bypass", ephemeral=True) + if bypass.id in roleping.bypass["users"]: + await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["users"]) == 10: @@ -141,18 +145,18 @@ class RolepingCog(Scale): ) return - matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], user.roles)) + matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], bypass.roles)) if matching_role: await ctx.send( - f"{user.mention} already has bypass via {matching_role[0].mention}", + f"{bypass.mention} already has bypass via {matching_role[0].mention}", ephemeral=True, ) return - roleping.bypass["users"].append(user.id) - roleping.save() - await ctx.send(f"{user.display_name} user bypass added for `{rping.name}`") + roleping.bypass["users"].append(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass added for `{role.name}`") @slash_command( name="roleping", @@ -160,19 +164,23 @@ class RolepingCog(Scale): sub_cmd_name="role", description="Add a role as a bypass to roleping", ) - @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="Role to add", opt_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + async def _roleping_bypass_role( + self, ctx: InteractionContext, bypass: Role, role: Role + ) -> None: + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", ephemeral=True) + if bypass.id in roleping.bypass["roles"]: + await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["roles"]) == 10: @@ -183,9 +191,9 @@ class RolepingCog(Scale): ) return - roleping.bypass["roles"].append(role.id) - roleping.save() - await ctx.send(f"{role.name} role bypass added for `{rping.name}`") + roleping.bypass["roles"].append(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.name} role bypass added for `{role.name}`") @slash_command( name="roleping", @@ -196,27 +204,27 @@ class RolepingCog(Scale): sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="user", description="User to remove", opt_type=OptionTypes.USER, required=True + name="bypass", description="User to remove", opt_type=OptionTypes.USER, required=True ) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_restore_user( - self, ctx: InteractionContext, user: Member, rping: Role + self, ctx: InteractionContext, bypass: Member, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if user.id not in roleping.bypass["users"]: - await ctx.send(f"{user.mention} not in bypass", ephemeral=True) + if bypass.id not in roleping.bypass.users: + await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True) return - roleping.bypass["users"].delete(user.id) - roleping.save() - await ctx.send(f"{user.display_name} user bypass removed for `{rping.name}`") + roleping.bypass.users.remove(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") @slash_command( name="roleping", @@ -225,32 +233,24 @@ class RolepingCog(Scale): description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_restore_role( - self, ctx: InteractionContext, role: Role, rping: Role + self, ctx: InteractionContext, bypass: Role, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", ephemeral=True) + if bypass.id not in roleping.bypass.roles: + await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True) return - if len(roleping.bypass["roles"]) == 10: - await ctx.send( - "Already have 10 roles in bypass. " - "Please consider consolidating roles for roleping bypass", - ephemeral=True, - ) - return - - roleping.bypass["roles"].append(role.id) - roleping.save() - await ctx.send(f"{role.name} role bypass added for `{rping.name}`") + roleping.bypass.roles.remove(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 7145cca..7e60987 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,6 +1,8 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale +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.snek.application_commands import ( OptionTypes, @@ -9,20 +11,16 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db.models import Warning -from jarvis.db.models import Warning from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field +from jarvis.utils.embeds import warning_embed from jarvis.utils.permissions import admin_or_permissions -class WarningCog(CacheCog): +class WarningCog(Scale): """J.A.R.V.I.S. WarningCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command(name="warn", description="Warn a user") @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -51,23 +49,15 @@ class WarningCog(CacheCog): await ctx.send("Duration must be < 5 days", ephemeral=True) return await ctx.defer() - _ = Warning( + await Warning( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, active=True, - ).save() - fields = [Field("Reason", reason, False)] - embed = build_embed( - title="Warning", - description=f"{user.mention} has been warned", - fields=fields, - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") - + ).commit() + embed = warning_embed(user, reason) await ctx.send(embed=embed) @slash_command(name="warnings", description="Get count of user warnings") @@ -86,17 +76,19 @@ class WarningCog(CacheCog): async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: active = bool(active) - warnings = Warning.objects( - user=user.id, - guild=ctx.guild.id, - ).order_by("-created_at") - active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by( - "-created_at" + warnings = ( + await Warning.find( + user=user.id, + guild=ctx.guild.id, + ) + .sort("created_at", -1) + .to_list(None) ) + active_warns = get_all(warnings, active=True) pages = [] if active: - if active_warns.count() == 0: + if len(active_warns) == 0: embed = build_embed( title="Warnings", description=f"{warnings.count()} total | 0 currently active", @@ -113,7 +105,7 @@ class WarningCog(CacheCog): if admin: admin_name = f"{admin.username}#{admin.discriminator}" fields.append( - Field( + EmbedField( name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"), value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b", inline=False, @@ -123,7 +115,7 @@ class WarningCog(CacheCog): embed = build_embed( title="Warnings", description=( - f"{warnings.count()} total | {active_warns.count()} currently active" + f"{len(warnings)} total | {len(active_warns)} currently active" ), fields=fields[i : i + 5], ) @@ -140,7 +132,7 @@ class WarningCog(CacheCog): title = "[A] " if warn.active else "[I] " title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC") fields.append( - Field( + EmbedField( name=title, value=warn.reason + "\n\u200b", inline=False, @@ -149,9 +141,7 @@ class WarningCog(CacheCog): for i in range(0, len(fields), 5): embed = build_embed( title="Warnings", - description=( - f"{warnings.count()} total | {active_warns.count()} currently active" - ), + description=(f"{len(warnings)} total | {len(active_warns)} currently active"), fields=fields[i : i + 5], ) embed.set_author( From 81f1bc5cd851b3d473585fc8d56296c6ed9f3bd2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:29:30 -0600 Subject: [PATCH 33/34] Various small unfinished changes --- jarvis/cogs/admin/mute.py | 242 ++++++++++++++++++------------------- jarvis/cogs/settings.py | 22 ++-- poetry.lock | 243 ++++++++++++++++++++++++-------------- 3 files changed, 289 insertions(+), 218 deletions(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 4be1910..5ac5ec5 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,122 +1,122 @@ """J.A.R.V.I.S. MuteCog.""" -# from datetime import datetime -# -# from dis_snek import InteractionContext, Permissions, Scale, 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 ( -# OptionTypes, -# SlashCommandChoice, -# slash_command, -# slash_option, -# ) -# from dis_snek.models.snek.command import check -# -# from jarvis.db.models import Mute -# from jarvis.utils import build_embed -# from jarvis.utils.permissions import admin_or_permissions -# -# -# class MuteCog(Scale): -# """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( -# name="reason", -# description="Reason for mute", -# opt_type=OptionTypes.STRING, -# required=True, -# ) -# @slash_option( -# name="time", -# description="Duration of mute, default 1", -# opt_type=OptionTypes.INTEGER, -# required=False, -# ) -# @slash_option( -# name="scale", -# description="Time scale, default Hour(s)", -# opt_type=OptionTypes.INTEGER, -# required=False, -# choices=[ -# SlashCommandChoice(name="Minute(s)", value=1), -# SlashCommandChoice(name="Hour(s)", value=60), -# SlashCommandChoice(name="Day(s)", value=3600), -# SlashCommandChoice(name="Week(s)", value=604800), -# ], -# ) -# @check( -# admin_or_permissions( -# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS -# ) -# ) -# async def _timeout( -# self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 -# ) -> None: -# if user == ctx.author: -# await ctx.send("You cannot mute yourself.", ephemeral=True) -# return -# if user == self.bot.user: -# await ctx.send("I'm afraid I can't let you do that", ephemeral=True) -# return -# if len(reason) > 100: -# await ctx.send("Reason must be < 100 characters", ephemeral=True) -# return -# -# # Max 4 weeks (2419200 seconds) per API -# duration = time * scale -# if duration > 2419200: -# await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) -# return -# -# await user.timeout(communication_disabled_until=duration, reason=reason) -# m = Mute( -# user=user.id, -# reason=reason, -# admin=ctx.author.id, -# guild=ctx.guild.id, -# duration=duration, -# active=True, -# ) -# await m.commit() -# -# embed = build_embed( -# title="User Muted", -# description=f"{user.mention} has been muted", -# fields=[EmbedField(name="Reason", value=reason)], -# ) -# 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}") -# await ctx.send(embed=embed) -# -# @slash_command(name="unmute", description="Unmute a user") -# @slash_option( -# name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True -# ) -# @check( -# admin_or_permissions( -# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS -# ) -# ) -# async def _unmute(self, ctx: InteractionContext, user: Member) -> None: -# if ( -# not user.communication_disabled_until -# or user.communication_disabled_until < datetime.now() # noqa: W503 -# ): -# await ctx.send("User is not muted", ephemeral=True) -# return -# -# embed = build_embed( -# title="User Unmuted", -# description=f"{user.mention} has been unmuted", -# fields=[], -# ) -# 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}") -# await ctx.send(embed=embed) +from datetime import datetime + +from dis_snek import InteractionContext, Permissions, Scale, 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 ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db.models import Mute + +from jarvis.utils import build_embed +from jarvis.utils.permissions import admin_or_permissions + + +class MuteCog(Scale): + """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( + name="reason", + description="Reason for mute", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="time", + description="Duration of mute, default 1", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="scale", + description="Time scale, default Hour(s)", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[ + SlashCommandChoice(name="Minute(s)", value=1), + SlashCommandChoice(name="Hour(s)", value=60), + SlashCommandChoice(name="Day(s)", value=3600), + SlashCommandChoice(name="Week(s)", value=604800), + ], + ) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _timeout( + self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 + ) -> None: + if user == ctx.author: + await ctx.send("You cannot mute yourself.", ephemeral=True) + return + if user == self.bot.user: + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) + return + if len(reason) > 100: + await ctx.send("Reason must be < 100 characters", ephemeral=True) + return + + # Max 4 weeks (2419200 seconds) per API + duration = time * scale + if duration > 2419200: + await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) + return + + await user.timeout(communication_disabled_until=duration, reason=reason) + m = Mute( + user=user.id, + reason=reason, + admin=ctx.author.id, + guild=ctx.guild.id, + duration=duration, + active=True, + ) + await m.commit() + + embed = build_embed( + title="User Muted", + description=f"{user.mention} has been muted", + fields=[EmbedField(name="Reason", value=reason)], + ) + 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}") + await ctx.send(embed=embed) + + @slash_command(name="unmute", description="Unmute a user") + @slash_option( + name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True + ) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _unmute(self, ctx: InteractionContext, user: Member) -> None: + if ( + not user.communication_disabled_until + or user.communication_disabled_until < datetime.now() # noqa: W503 + ): + await ctx.send("User is not muted", ephemeral=True) + return + + embed = build_embed( + title="User Unmuted", + description=f"{user.mention} has been unmuted", + fields=[], + ) + 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}") + await ctx.send(embed=embed) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index d761bc1..c197573 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -63,24 +63,24 @@ class SettingsCog(commands.Cog): @cog_ext.cog_subcommand( base="settings", subcommand_group="set", - name="userlog", - description="Set userlog channel", + name="activitylog", + description="Set activitylog channel", choices=[ create_option( name="channel", - description="Userlog channel", + description="Activitylog channel", opt_type=7, required=True, ) ], ) @check(admin_or_permissions(manage_guild=True)) - async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None: + 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) return - self.update_settings("userlog", channel.id, ctx.guild.id) - await ctx.send(f"Settings applied. New userlog channel is {channel.mention}") + 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", @@ -176,12 +176,12 @@ class SettingsCog(commands.Cog): @cog_ext.cog_subcommand( base="settings", subcommand_group="unset", - name="userlog", - description="Unset userlog channel", + name="activitylog", + description="Unset activitylog channel", ) @check(admin_or_permissions(manage_guild=True)) - async def _unset_userlog(self, ctx: SlashContext) -> None: - self.delete_settings("userlog", ctx.guild.id) + async def _unset_activitylog(self, ctx: SlashContext) -> None: + self.delete_settings("activitylog", ctx.guild.id) await ctx.send("Setting removed.") @cog_ext.cog_subcommand( @@ -234,7 +234,7 @@ class SettingsCog(commands.Cog): value = value.mention else: value = "||`[redacted]`||" - elif setting.setting in ["userlog", "modlog"]: + elif setting.setting in ["activitylog", "modlog"]: value = find(lambda x: x.id == value, ctx.guild.text_channels) if value: value = value.mention diff --git a/poetry.lock b/poetry.lock index 2d8bfcf..cf974fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -117,7 +117,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -136,7 +136,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dis-snek" -version = "6.0.0" +version = "7.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -145,8 +145,20 @@ python-versions = ">=3.10" [package.dependencies] aiohttp = "*" attrs = "*" +discord-typings = "*" tomli = "*" +[[package]] +name = "discord-typings" +version = "0.3.1" +description = "Maintained typings of payloads that Discord sends" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing_extensions = ">=4,<5" + [[package]] name = "flake8" version = "4.0.1" @@ -181,7 +193,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "main" optional = false @@ -214,7 +226,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.6.0" +version = "0.6.1" description = "" category = "main" optional = false @@ -232,7 +244,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "8e3276a81575927e4484dfaa5c3e0e894e3c4707" +resolved_reference = "52a3d568030a79db8ad5ddf65c26216913598bf5" [[package]] name = "jedi" @@ -259,16 +271,19 @@ python-versions = ">=3.6" [[package]] name = "marshmallow" -version = "3.14.1" +version = "3.15.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[package.dependencies] +packaging = "*" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] -docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] -lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -322,7 +337,7 @@ python-versions = "*" [[package]] name = "numpy" -version = "1.22.2" +version = "1.22.3" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -343,7 +358,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "opencv-python" -version = "4.5.5.62" +version = "4.5.5.64" description = "Wrapper package for OpenCV python bindings." category = "main" optional = false @@ -359,12 +374,23 @@ numpy = [ [[package]] name = "orjson" -version = "3.6.6" +version = "3.6.7" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + [[package]] name = "parso" version = "0.8.3" @@ -418,7 +444,7 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.5.0" +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 @@ -523,9 +549,20 @@ srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] zstd = ["zstandard"] +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "python-gitlab" -version = "3.1.1" +version = "3.2.0" description = "Interact with GitLab API" category = "main" optional = false @@ -643,7 +680,7 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rope" -version = "0.22.0" +version = "0.23.0" description = "a python refactoring library..." category = "dev" optional = false @@ -686,22 +723,31 @@ python-versions = ">=3.7" [[package]] name = "tweepy" -version = "4.5.0" +version = "4.7.0" description = "Twitter library for Python" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] +oauthlib = ">=3.2.0,<4" requests = ">=2.27.0,<3" -requests-oauthlib = ">=1.0.0,<2" +requests-oauthlib = ">=1.2.0,<2" [package.extras] -async = ["aiohttp (>=3.7.3,<4)", "oauthlib (>=3.1.0,<4)"] +async = ["aiohttp (>=3.7.3,<4)"] dev = ["coveralls (>=2.1.0)", "tox (>=3.14.0)"] socks = ["requests[socks] (>=2.27.0,<3)"] test = ["vcrpy (>=1.10.3)"] +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "ujson" version = "5.1.0" @@ -910,16 +956,20 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {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"}, ] dis-snek = [ - {file = "dis-snek-6.0.0.tar.gz", hash = "sha256:3abe2af832bd87adced01ebc697e418b25871a4158ac8ba2e202d8fbb2921f44"}, - {file = "dis_snek-6.0.0-py3-none-any.whl", hash = "sha256:106cb08b0cc982c59db31be01ee529e4d7273835de7f418562d8e6b24d7f965d"}, + {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, + {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, +] +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"}, @@ -991,8 +1041,8 @@ gitdb = [ {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1047,8 +1097,8 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] marshmallow = [ - {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, - {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, + {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"}, @@ -1128,64 +1178,77 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] numpy = [ - {file = "numpy-1.22.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:515a8b6edbb904594685da6e176ac9fbea8f73a5ebae947281de6613e27f1956"}, - {file = "numpy-1.22.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76a4f9bce0278becc2da7da3b8ef854bed41a991f4226911a24a9711baad672c"}, - {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:168259b1b184aa83a514f307352c25c56af111c269ffc109d9704e81f72e764b"}, - {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3556c5550de40027d3121ebbb170f61bbe19eb639c7ad0c7b482cd9b560cd23b"}, - {file = "numpy-1.22.2-cp310-cp310-win_amd64.whl", hash = "sha256:aafa46b5a39a27aca566198d3312fb3bde95ce9677085efd02c86f7ef6be4ec7"}, - {file = "numpy-1.22.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:55535c7c2f61e2b2fc817c5cbe1af7cb907c7f011e46ae0a52caa4be1f19afe2"}, - {file = "numpy-1.22.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:60cb8e5933193a3cc2912ee29ca331e9c15b2da034f76159b7abc520b3d1233a"}, - {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b536b6840e84c1c6a410f3a5aa727821e6108f3454d81a5cd5900999ef04f89"}, - {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2638389562bda1635b564490d76713695ff497242a83d9b684d27bb4a6cc9d7a"}, - {file = "numpy-1.22.2-cp38-cp38-win32.whl", hash = "sha256:6767ad399e9327bfdbaa40871be4254d1995f4a3ca3806127f10cec778bd9896"}, - {file = "numpy-1.22.2-cp38-cp38-win_amd64.whl", hash = "sha256:03ae5850619abb34a879d5f2d4bb4dcd025d6d8fb72f5e461dae84edccfe129f"}, - {file = "numpy-1.22.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d76a26c5118c4d96e264acc9e3242d72e1a2b92e739807b3b69d8d47684b6677"}, - {file = "numpy-1.22.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15efb7b93806d438e3bc590ca8ef2f953b0ce4f86f337ef4559d31ec6cf9d7dd"}, - {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badca914580eb46385e7f7e4e426fea6de0a37b9e06bec252e481ae7ec287082"}, - {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dd11d9f13ea1be17bac39c1942f527cbf7065f94953cf62dfe805653da2f8f"}, - {file = "numpy-1.22.2-cp39-cp39-win32.whl", hash = "sha256:8cf33634b60c9cef346663a222d9841d3bbbc0a2f00221d6bcfd0d993d5543f6"}, - {file = "numpy-1.22.2-cp39-cp39-win_amd64.whl", hash = "sha256:59153979d60f5bfe9e4c00e401e24dfe0469ef8da6d68247439d3278f30a180f"}, - {file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"}, - {file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"}, + {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"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"}, + {file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"}, + {file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"}, + {file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"}, + {file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"}, + {file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"}, + {file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"}, + {file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"}, + {file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"}, ] oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, ] opencv-python = [ - {file = "opencv-python-4.5.5.62.tar.gz", hash = "sha256:3efe232b32d5e1327e7c82bc6d61230737821c5190ce5c783e64a1bc8d514e18"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:2601388def0d6b957cc30dd88f8ff74a5651ae6940dd9e488241608cfa2b15c7"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71fdc49df412b102d97f14927321309043c79c4a3582cce1dc803370ff9c39c0"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:130cc75d56b29aa3c5de8b6ac438242dd2574ba6eaa8bccdffdcfd6b78632f7f"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-win32.whl", hash = "sha256:3a75c7ad45b032eea0c72e389aac6dd435f5c87e87f60237095c083400bc23aa"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-win_amd64.whl", hash = "sha256:c463d2276d8662b972d20ca9644702188507de200ca5405b89e1fe71c5c99989"}, - {file = "opencv_python-4.5.5.62-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:ac92e743e22681f30001942d78512c1e39bce53dbffc504e5645fdc45c0f2c47"}, + {file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"}, + {file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"}, ] orjson = [ - {file = "orjson-3.6.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e4a7cad6c63306318453980d302c7c0b74c0cc290dd1f433bbd7d31a5af90cf1"}, - {file = "orjson-3.6.6-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e533941dca4a0530a876de32e54bf2fd3269cdec3751aebde7bfb5b5eba98e74"}, - {file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:9adf63be386eaa34278967512b83ff8fc4bed036a246391ae236f68d23c47452"}, - {file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:3b636753ae34d4619b11ea7d664a2f1e87e55e9738e5123e12bcce22acae9d13"}, - {file = "orjson-3.6.6-cp310-none-win_amd64.whl", hash = "sha256:78a10295ed048fd916c6584d6d27c232eae805a43e7c14be56e3745f784f0eb6"}, - {file = "orjson-3.6.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:82b4f9fb2af7799b52932a62eac484083f930d5519560d6f64b24d66a368d03f"}, - {file = "orjson-3.6.6-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a0033d07309cc7d8b8c4bc5d42f0dd4422b53ceb91dee9f4086bb2afa70b7772"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b321f99473116ab7c7c028377372f7b4adba4029aaca19cd567e83898f55579"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:b9c98ed94f1688cc11b5c61b8eea39d854a1a2f09f71d8a5af005461b14994ed"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:00b333a41392bd07a8603c42670547dbedf9b291485d773f90c6470eff435608"}, - {file = "orjson-3.6.6-cp37-none-win_amd64.whl", hash = "sha256:8d4fd3bdee65a81f2b79c50937d4b3c054e1e6bfa3fc72ed018a97c0c7c3d521"}, - {file = "orjson-3.6.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:954c9f8547247cd7a8c91094ff39c9fe314b5eaeaec90b7bfb7384a4108f416f"}, - {file = "orjson-3.6.6-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:74e5aed657ed0b91ef05d44d6a26d3e3e12ce4d2d71f75df41a477b05878c4a9"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4008a5130e6e9c33abaa95e939e0e755175da10745740aa6968461b2f16830e2"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:012761d5f3d186deb4f6238f15e9ea7c1aac6deebc8f5b741ba3b4fafe017460"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:b464546718a940b48d095a98df4c04808bfa6c8706fe751fc3f9390bc2f82643"}, - {file = "orjson-3.6.6-cp38-none-win_amd64.whl", hash = "sha256:f10a800f4e5a4aab52076d4628e9e4dab9370bdd9d8ea254ebfde846b653ab25"}, - {file = "orjson-3.6.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8010d2610cfab721725ef14d578c7071e946bbdae63322d8f7b49061cf3fde8d"}, - {file = "orjson-3.6.6-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8dca67a4855e1e0f9a2ea0386e8db892708522e1171dc0ddf456932288fbae63"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af065d60523139b99bd35b839c7a2d8c5da55df8a8c4402d2eb6cdc07fa7a624"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:fa1f389cc9f766ae0cf7ba3533d5089836b01a5ccb3f8d904297f1fcf3d9dc34"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:ec1221ad78f94d27b162a1d35672b62ef86f27f0e4c2b65051edb480cc86b286"}, - {file = "orjson-3.6.6-cp39-none-win_amd64.whl", hash = "sha256:afed2af55eeda1de6b3f1cbc93431981b19d380fcc04f6ed86e74c1913070304"}, - {file = "orjson-3.6.6.tar.gz", hash = "sha256:55dd988400fa7fbe0e31407c683f5aaab013b5bd967167b8fe058186773c4d6c"}, + {file = "orjson-3.6.7-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93188a9d6eb566419ad48befa202dfe7cd7a161756444b99c4ec77faea9352a4"}, + {file = "orjson-3.6.7-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82515226ecb77689a029061552b5df1802b75d861780c401e96ca6bc8495f775"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3af57ffab7848aaec6ba6b9e9b41331250b57bf696f9d502bacdc71a0ebab0ba"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:a7297504d1142e7efa236ffc53f056d73934a993a08646dbcee89fc4308a8fcf"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:5a50cde0dbbde255ce751fd1bca39d00ecd878ba0903c0480961b31984f2fab7"}, + {file = "orjson-3.6.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d21f9a2d1c30e58070f93988db4cad154b9009fafbde238b52c1c760e3607fbe"}, + {file = "orjson-3.6.7-cp310-none-win_amd64.whl", hash = "sha256:e152464c4606b49398afd911777decebcf9749cc8810c5b4199039e1afb0991e"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:0a65f3c403f38b0117c6dd8e76e85a7bd51fcd92f06c5598dfeddbc44697d3e5"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c47cfca18e41f7f37b08ff3e7abf5ada2d0f27b5ade934f05be5fc5bb956e9d"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63185af814c243fad7a72441e5f98120c9ecddf2675befa486d669fb65539e9b"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2da6fde42182b80b40df2e6ab855c55090ebfa3fcc21c182b7ad1762b61d55c"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:48c5831ec388b4e2682d4ff56d6bfa4a2ef76c963f5e75f4ff4785f9cf338a80"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:913fac5d594ccabf5e8fbac15b9b3bb9c576d537d49eeec9f664e7a64dde4c4b"}, + {file = "orjson-3.6.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:58f244775f20476e5851e7546df109f75160a5178d44257d437ba6d7e562bfe8"}, + {file = "orjson-3.6.7-cp37-none-win_amd64.whl", hash = "sha256:2d5f45c6b85e5f14646df2d32ecd7ff20fcccc71c0ea1155f4d3df8c5299bbb7"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:612d242493afeeb2068bc72ff2544aa3b1e627578fcf92edee9daebb5893ffea"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:539cdc5067db38db27985e257772d073cd2eb9462d0a41bde96da4e4e60bd99b"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d103b721bbc4f5703f62b3882e638c0b65fcdd48622531c7ffd45047ef8e87c"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb10a20f80e95102dd35dfbc3a22531661b44a09b55236b012a446955846b023"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:bb68d0da349cf8a68971a48ad179434f75256159fe8b0715275d9b49fa23b7a3"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:4a2c7d0a236aaeab7f69c17b7ab4c078874e817da1bfbb9827cb8c73058b3050"}, + {file = "orjson-3.6.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3be045ca3b96119f592904cf34b962969ce97bd7843cbfca084009f6c8d2f268"}, + {file = "orjson-3.6.7-cp38-none-win_amd64.whl", hash = "sha256:bd765c06c359d8a814b90f948538f957fa8a1f55ad1aaffcdc5771996aaea061"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7dd9e1e46c0776eee9e0649e3ae9584ea368d96851bcaeba18e217fa5d755283"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c4b4f20a1e3df7e7c83717aff0ef4ab69e42ce2fb1f5234682f618153c458406"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7107a5673fd0b05adbb58bf71c1578fc84d662d29c096eb6d998982c8635c221"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08b6940dd9a98ccf09785890112a0f81eadb4f35b51b9a80736d1725437e22c"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:f5d1648e5a9d1070f3628a69a7c6c17634dbb0caf22f2085eca6910f7427bf1f"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e6201494e8dff2ce7fd21da4e3f6dfca1a3fed38f9dcefc972f552f6596a7621"}, + {file = "orjson-3.6.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70d0386abe02879ebaead2f9632dd2acb71000b4721fd8c1a2fb8c031a38d4d5"}, + {file = "orjson-3.6.7-cp39-none-win_amd64.whl", hash = "sha256:d9a3288861bfd26f3511fb4081561ca768674612bac59513cb9081bb61fcc87f"}, + {file = "orjson-3.6.7.tar.gz", hash = "sha256:a4bb62b11289b7620eead2f25695212e9ac77fcfba76f050fa8a540fb5c32401"}, +] +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"}, @@ -1237,8 +1300,8 @@ pillow = [ {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] platformdirs = [ - {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, - {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, + {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"}, @@ -1435,9 +1498,13 @@ pymongo = [ {file = "pymongo-3.12.3-py2.7-macosx-10.14-intel.egg", hash = "sha256:d81299f63dc33cc172c26faf59cc54dd795fc6dd5821a7676cca112a5ee8bbd6"}, {file = "pymongo-3.12.3.tar.gz", hash = "sha256:0a89cadc0062a5e53664dde043f6c097172b8c1c5f0094490095282ff9995a5f"}, ] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] python-gitlab = [ - {file = "python-gitlab-3.1.1.tar.gz", hash = "sha256:cad1338c1ff1a791a7bae7995dc621e26c77dfbf225bade456acec7512782825"}, - {file = "python_gitlab-3.1.1-py3-none-any.whl", hash = "sha256:2a7de39c8976db6d0db20031e71b3e43d262e99e64b471ef09bf00482cd3d9fa"}, + {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"}, @@ -1495,8 +1562,8 @@ requests-toolbelt = [ {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] rope = [ - {file = "rope-0.22.0-py3-none-any.whl", hash = "sha256:2847220bf72ead09b5abe72b1edc9cacff90ab93663ece06913fc97324167870"}, - {file = "rope-0.22.0.tar.gz", hash = "sha256:b00fbc064a26fc62d7220578a27fd639b2fad57213663cc396c137e92d73f10f"}, + {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, + {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -1515,8 +1582,12 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tweepy = [ - {file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"}, - {file = "tweepy-4.5.0.tar.gz", hash = "sha256:12cc4b0a3d7b745928b08c3eb55a992236895e00028584d11fa41258f07df1b9"}, + {file = "tweepy-4.7.0-py2.py3-none-any.whl", hash = "sha256:d7e78c49232e849b660d82c7c2e504e8487d8014dcb73b39b490b61827965aba"}, + {file = "tweepy-4.7.0.tar.gz", hash = "sha256:82323505d549e3868e14a4570fc069ab3058ef95f9e578d1476d69bf2c831483"}, +] +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"}, ] ujson = [ {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, From b6551c6c37aa1202158b00ecdc11954d1a4a1f42 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:43:15 -0600 Subject: [PATCH 34/34] Bump version --- jarvis/__init__.py | 9 ++++++--- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 38c27d8..5c7e26c 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,14 +1,19 @@ """Main J.A.R.V.I.S. package.""" import logging +from importlib.metadata import version as _v from dis_snek import Intents from jarvis_core.db import connect -# from jarvis import logo # noqa: F401 from jarvis import utils from jarvis.client import Jarvis from jarvis.config import get_config +try: + __version__ = _v("jarvis") +except Exception: + __version__ = "0.0.0" + jconfig = get_config() logger = logging.getLogger("discord") @@ -23,8 +28,6 @@ restart_ctx = None jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) -__version__ = "2.0.0a1" - async def run() -> None: """Run J.A.R.V.I.S.""" diff --git a/pyproject.toml b/pyproject.toml index 354687e..d63854d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jarvis" -version = "2.0.0a1" +version = "2.0.0b1" description = "J.A.R.V.I.S. admin bot" authors = ["Zevaryx "]