diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6e7cd96 --- /dev/null +++ b/.flake8 @@ -0,0 +1,14 @@ +[flake8] +extend-ignore = + Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually + ANN1, # Ignore self and cls annotations + ANN204, ANN206, # return annotations for special methods and class methods + D105, D107, # Missing Docstrings in magic method and __init__ + S311, # Standard pseudo-random generators are not suitable for security/cryptographic purposes. + D401, # First line should be in imperative mood; try rephrasing + D400, # First line should end with a period + D101, # Missing docstring in public class + + # Plugins we don't currently include: flake8-return + R503, # missing explicit return at the end of function ableto return non-None value. +max-line-length=100 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e102ae9..b88c6fe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,15 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-toml - id: check-yaml + args: [--unsafe] - id: check-merge-conflict - id: requirements-txt-fixer - id: end-of-file-fixer + - id: debug-statements + language_version: python3.10 - id: trailing-whitespace args: [--markdown-linebreak-ext=md] @@ -16,23 +19,31 @@ repos: - id: python-check-blanket-noqa - repo: https://github.com/psf/black - rev: 21.7b0 + rev: 22.1.0 hooks: - id: black - args: [--line-length=120] + args: [--line-length=100, --target-version=py310] + language_version: python3.10 - repo: https://github.com/pre-commit/mirrors-isort - rev: V5.9.3 + rev: V5.10.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: - flake8-annotations~=2.0 - flake8-bandit~=2.1 - flake8-docstrings~=1.5 - args: [--max-line-length=120, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204] + - flake8-bugbear + - flake8-comprehensions + - flake8-quotes + - flake8-raise + - flake8-deprecated + - flake8-print + - flake8-return + language_version: python3.10 diff --git a/README.md b/README.md index 9f01850..2f0b019 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ +
+ J.A.R.V.I.S + +# Just Another Rather Very Intelligent System +
+ + [![python 3.8+](https://img.shields.io/badge/python-3.8+-blue)]() [![tokei lines of code](https://tokei.rs/b1/git.zevaryx.com/stark-industries/j.a.r.v.i.s.?category=code)](https://git.zevaryx.com/stark-industries/j.a.r.v.i.s.) [![discord chat widget](https://img.shields.io/discord/862402786116763668?style=social&logo=discord)](https://discord.gg/VtgZntXcnZ) +
-
-J.A.R.V.I.S - -# Just Another Very Intelligent System (J.A.R.V.I.S.) Welcome to the J.A.R.V.I.S. Initiative! While the main goal is to create the best discord bot there can be, a great achievement would be to present him to the Robots and have him integrated into the dbrand server. Feel free to suggest anything you may think to be useful… or cool. **Note:** Some commands have been custom made to be used in the dbrand server. @@ -34,19 +38,17 @@ If you wish to contribute to the J.A.R.V.I.S codebase or documentation, join the Join the [Stark R&D Department Discord server](https://discord.gg/VtgZntXcnZ) to be kept up-to-date on code updates and issues. ## Requirements -- MongoDB 4.4 or higher -- Python 3.8 or higher +- MongoDB 5.0 or higher +- Python 3.10 or higher - [tokei](https://github.com/XAMPPRocky/tokei) 12.1 or higher On top of the above requirements, the following pip packages are also required: -- `discord-py>=1.7, <2` +- `dis-snek>=5.0.0` - `psutil>=5.8, <6` - `GitPython>=3.1, <4` - `PyYaml>=5.4, <6` -- `discord-py-slash-command>=2.3.2, <3` - `pymongo>=3.12.0, <4` - `opencv-python>=4.5, <5` -- `ButtonPaginator>=0.0.3` - `Pillow>=8.2.0, <9` - `python-gitlab>=2.9.0, <3` - `ulid-py>=1.1.0, <2` diff --git a/jarvis.png b/jarvis.png index cf2071a..8164087 100644 Binary files a/jarvis.png and b/jarvis.png differ diff --git a/jarvis.svg b/jarvis.svg index eb8b3f4..fd13b16 100644 --- a/jarvis.svg +++ b/jarvis.svg @@ -1,16 +1,16 @@ - + - logotests + jarvis - - - - + + + + diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 6307429..9766919 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,20 +1,13 @@ """Main J.A.R.V.I.S. package.""" -import asyncio import logging -from pathlib import Path -from typing import Optional -from discord import Intents -from discord.ext import commands -from discord.utils import find -from discord_slash import SlashCommand +from dis_snek import Intents, Snake, listen from mongoengine import connect -from psutil import Process -from jarvis import logo # noqa: F401 +# from jarvis import logo # noqa: F401 from jarvis import tasks, utils from jarvis.config import get_config -from jarvis.events import guild, member, message +from jarvis.events import member, message jconfig = get_config() @@ -24,53 +17,32 @@ 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) -if asyncio.get_event_loop().is_closed(): - asyncio.set_event_loop(asyncio.new_event_loop()) - -intents = Intents.default() +intents = Intents.DEFAULT intents.members = True restart_ctx = None -jarvis = commands.Bot( - command_prefix=utils.get_prefix, - intents=intents, - help_command=None, - max_messages=jconfig.max_messages, -) +jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) -slash = SlashCommand(jarvis, sync_commands=False, sync_on_cog_reload=True) -jarvis_self = Process() -__version__ = "1.11.4" +__version__ = "2.0.0a0" -@jarvis.event +@listen() async def on_ready() -> None: - """d.py on_ready override.""" + """Lepton on_ready override.""" global restart_ctx - print(" Logged in as {0.user}".format(jarvis)) - print(" Connected to {} guild(s)".format(len(jarvis.guilds))) - with jarvis_self.oneshot(): - print(f" Current PID: {jarvis_self.pid}") - Path(f"jarvis.{jarvis_self.pid}.pid").touch() - if restart_ctx: - channel = None - if "guild" in restart_ctx: - guild = find(lambda x: x.id == restart_ctx["guild"], jarvis.guilds) - if guild: - channel = find(lambda x: x.id == restart_ctx["channel"], guild.channels) - elif "user" in restart_ctx: - channel = jarvis.get_user(restart_ctx["user"]) - if channel: - await channel.send("Core systems restarted and back online.") - restart_ctx = None + print(" Logged in as {0.user}".format(jarvis)) # noqa: T001 + print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 -def run(ctx: dict = None) -> Optional[dict]: +@listen() +async def on_startup() -> None: + """Lepton on_startup override.""" + tasks.init() + + +def run() -> None: """Run J.A.R.V.I.S.""" - global restart_ctx - if ctx: - restart_ctx = ctx connect( db="ctc2", alias="ctc2", @@ -84,27 +56,21 @@ def run(ctx: dict = None) -> Optional[dict]: **jconfig.mongo["connect"], ) jconfig.get_db_config() + for extension in utils.get_extensions(): jarvis.load_extension(extension) - print( + + print( # noqa: T001 " https://discord.com/api/oauth2/authorize?client_id=" - + "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) # noqa: W503 + "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) ) jarvis.max_messages = jconfig.max_messages - tasks.init() # Add event listeners if jconfig.events: _ = [ - guild.GuildEventHandler(jarvis), member.MemberEventHandler(jarvis), message.MessageEventHandler(jarvis), ] - jarvis.run(jconfig.token, bot=True, reconnect=True) - for cog in jarvis.cogs: - session = getattr(cog, "_session", None) - if session: - session.close() - if restart_ctx: - return restart_ctx + jarvis.start(jconfig.token) diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index 0c76e20..b11ccd4 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,16 +1,16 @@ """J.A.R.V.I.S. Admin Cogs.""" -from discord.ext.commands import Bot +from dis_snek import Snake -from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning +from jarvis.cogs.admin import ban, kick, mute, purge, roleping, warning -def setup(bot: Bot) -> None: +def setup(bot: Snake) -> None: """Add admin cogs to J.A.R.V.I.S.""" - bot.add_cog(ban.BanCog(bot)) - bot.add_cog(kick.KickCog(bot)) - bot.add_cog(lock.LockCog(bot)) - bot.add_cog(lockdown.LockdownCog(bot)) - bot.add_cog(mute.MuteCog(bot)) - bot.add_cog(purge.PurgeCog(bot)) - bot.add_cog(roleping.RolepingCog(bot)) - bot.add_cog(warning.WarningCog(bot)) + ban.BanCog(bot) + kick.KickCog(bot) + # lock.LockCog(bot) + # lockdown.LockdownCog(bot) + mute.MuteCog(bot) + purge.PurgeCog(bot) + roleping.RolepingCog(bot) + warning.WarningCog(bot) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 646049d..d72dd08 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -2,30 +2,33 @@ import re from datetime import datetime, timedelta -from ButtonPaginator import Paginator -from discord import User -from discord.ext import commands -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.model import ButtonStyle -from discord_slash.utils.manage_commands import create_choice, create_option +from dis_snek import InteractionContext, Permissions, Snake +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, + SlashCommandChoice, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check from jarvis.db.models import Ban, Unban -from jarvis.utils import build_embed +from jarvis.utils import build_embed, find, find_all from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions class BanCog(CacheCog): """J.A.R.V.I.S. BanCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): super().__init__(bot) async def discord_apply_ban( self, - ctx: SlashContext, + ctx: InteractionContext, reason: str, user: User, duration: int, @@ -37,7 +40,7 @@ class BanCog(CacheCog): await ctx.guild.ban(user, reason=reason) _ = Ban( user=user.id, - username=user.name, + username=user.username, discrim=user.discriminator, reason=reason, admin=ctx.author.id, @@ -54,20 +57,20 @@ class BanCog(CacheCog): ) embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, + name=user.display_name, + icon_url=user.avatar, ) - embed.set_thumbnail(url=user.avatar_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + embed.set_thumbnail(url=user.avatar) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) - async def discord_apply_unban(self, ctx: SlashContext, user: User, reason: str) -> None: + 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( user=user.id, - username=user.name, + username=user.username, discrim=user.discriminator, guild=ctx.guild.id, admin=ctx.author.id, @@ -77,77 +80,56 @@ class BanCog(CacheCog): embed = build_embed( title="User Unbanned", description=f"<@{user.id}> was unbanned", - fields=[Field(name="Reason", value=reason)], + fields=[EmbedField(name="Reason", value=reason)], ) embed.set_author( - name=user.name, - icon_url=user.avatar_url, + name=user.username, + icon_url=user.avatar, ) - embed.set_thumbnail(url=user.avatar_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + embed.set_thumbnail(url=user.avatar) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="ban", - description="Ban a user", - options=[ - create_option( - name="user", - description="User to ban", - option_type=6, - required=True, - ), - create_option( - name="reason", - description="Ban reason", - required=True, - option_type=3, - ), - create_option( - name="btype", - description="Ban type", - option_type=3, - required=False, - choices=[ - create_choice(value="perm", name="Permanent"), - create_choice(value="temp", name="Temporary"), - create_choice(value="soft", name="Soft"), - ], - ), - create_option( - name="duration", - description="Ban duration in hours if temporary", - required=False, - option_type=4, - ), + @slash_command(name="ban", description="Ban a user") + @slash_option(name="user", description="User to ban", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="reason", description="Ban reason", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="btype", + description="Ban type", + opt_type=OptionTypes.STRING, + required=True, + choices=[ + SlashCommandChoice(name="Permanent", value="perm"), + SlashCommandChoice(name="Temporary", value="temp"), + SlashCommandChoice(name="Soft", value="soft"), ], ) - @admin_or_permissions(ban_members=True) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _ban( self, - ctx: SlashContext, + ctx: InteractionContext, + reason: str, user: User = None, - reason: str = None, btype: str = "perm", duration: int = 4, ) -> None: if not user or user == ctx.author: - await ctx.send("You cannot ban yourself.", hidden=True) + await ctx.send("You cannot ban yourself.", ephemeral=True) return if user == self.bot.user: - await ctx.send("I'm afraid I can't let you do that", hidden=True) + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) return if btype == "temp" and duration < 0: - await ctx.send("You cannot set a temp ban to < 0 hours.", hidden=True) + await ctx.send("You cannot set a temp ban to < 0 hours.", ephemeral=True) return elif btype == "temp" and duration > 744: - await ctx.send("You cannot set a temp ban to > 1 month", hidden=True) + await ctx.send("You cannot set a temp ban to > 1 month", ephemeral=True) return if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", hidden=True) + await ctx.send("Reason must be < 100 characters", ephemeral=True) return - if not reason: - reason = "Mr. Stark is displeased with your presence. Please leave." await ctx.defer() @@ -160,10 +142,10 @@ class BanCog(CacheCog): if mtype == "temp": user_message += f"\nDuration: {duration} hours" - fields = [Field(name="Type", value=mtype)] + fields = [EmbedField(name="Type", value=mtype)] if mtype == "temp": - fields.append(Field(name="Duration", value=f"{duration} hour(s)")) + fields.append(EmbedField(name="Duration", value=f"{duration} hour(s)")) user_embed = build_embed( title=f"You have been banned from {ctx.guild.name}", @@ -172,10 +154,10 @@ class BanCog(CacheCog): ) user_embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.avatar, ) - user_embed.set_thumbnail(url=ctx.guild.icon_url) + user_embed.set_thumbnail(url=ctx.guild.icon.url) try: await user.send(embed=user_embed) @@ -184,13 +166,13 @@ class BanCog(CacheCog): try: await ctx.guild.ban(user, reason=reason) except Exception as e: - await ctx.send(f"Failed to ban user:\n```\n{e}\n```", hidden=True) + await ctx.send(f"Failed to ban user:\n```\n{e}\n```", ephemeral=True) return send_failed = False if mtype == "soft": await ctx.guild.unban(user, reason="Ban was softban") - fields.append(Field(name="DM Sent?", value=str(not send_failed))) + fields.append(EmbedField(name="DM Sent?", value=str(not send_failed))) if btype != "temp": duration = None active = True @@ -199,33 +181,22 @@ class BanCog(CacheCog): await self.discord_apply_ban(ctx, reason, user, duration, active, fields, mtype) - @cog_ext.cog_slash( - name="unban", - description="Unban a user", - options=[ - create_option( - name="user", - description="User to unban", - option_type=3, - required=True, - ), - create_option( - name="reason", - description="Unban reason", - required=True, - option_type=3, - ), - ], + @slash_command(name="unban", description="Unban a user") + @slash_option( + name="user", description="User to unban", opt_type=OptionTypes.STRING, required=True ) - @admin_or_permissions(ban_members=True) + @slash_option( + name="reason", description="Unban reason", opt_type=OptionTypes.STRING, required=True + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _unban( self, - ctx: SlashContext, + ctx: InteractionContext, user: str, reason: str, ) -> None: if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", hidden=True) + await ctx.send("Reason must be < 100 characters", ephemeral=True) return orig_user = user @@ -236,26 +207,31 @@ class BanCog(CacheCog): bans = await ctx.guild.bans() # Try to get ban information out of Discord - if re.match("^[0-9]{1,}$", user): # User ID + if re.match(r"^[0-9]{1,}$", user): # User ID user = int(user) discord_ban_info = find(lambda x: x.user.id == user, bans) else: # User name - if re.match("#[0-9]{4}$", user): # User name has discrim + if re.match(r"#[0-9]{4}$", user): # User name has discrim user, discrim = user.split("#") if discrim: discord_ban_info = find( - lambda x: x.user.name == user and x.user.discriminator == discrim, + lambda x: x.user.username == user and x.user.discriminator == discrim, bans, ) else: - results = [x for x in filter(lambda x: x.user.name == user, bans)] + results = find_all(lambda x: x.user.username == user, bans) if results: if len(results) > 1: active_bans = [] for ban in bans: - active_bans.append("{0} ({1}): {2}".format(ban.user.name, ban.user.id, ban.reason)) + active_bans.append( + "{0} ({1}): {2}".format(ban.user.username, ban.user.id, ban.reason) + ) ab_message = "\n".join(active_bans) - message = f"More than one result. Please use one of the following IDs:\n```{ab_message}\n```" + message = ( + "More than one result. " + f"Please use one of the following IDs:\n```{ab_message}\n```" + ) await ctx.send(message) return else: @@ -278,7 +254,7 @@ class BanCog(CacheCog): database_ban_info = Ban.objects(**search).first() if not discord_ban_info and not database_ban_info: - await ctx.send(f"Unable to find user {orig_user}", hidden=True) + await ctx.send(f"Unable to find user {orig_user}", ephemeral=True) elif discord_ban_info: await self.discord_apply_unban(ctx, discord_ban_info.user, reason) @@ -297,46 +273,41 @@ 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." + ) - @cog_ext.cog_subcommand( - base="bans", - name="list", - description="List bans", - options=[ - create_option( - name="type", - description="Ban type", - option_type=4, - required=False, - choices=[ - create_choice(value=0, name="All"), - create_choice(value=1, name="Permanent"), - create_choice(value=2, name="Temporary"), - create_choice(value=3, name="Soft"), - ], - ), - create_option( - name="active", - description="Active bans", - option_type=4, - required=False, - choices=[ - create_choice(value=1, name="Yes"), - create_choice(value=0, name="No"), - ], - ), + @slash_command( + name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans" + ) + @slash_option( + name="btype", + description="Ban type", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[ + SlashCommandChoice(name="All", value=0), + SlashCommandChoice(name="Permanent", value=1), + SlashCommandChoice(name="Temporary", value=2), + SlashCommandChoice(name="Soft", value=3), ], ) - @admin_or_permissions(ban_members=True) - async def _bans_list(self, ctx: SlashContext, type: int = 0, active: int = 1) -> None: + @slash_option( + name="active", + description="Active bans", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None: active = bool(active) exists = self.check_cache(ctx, type=type, active=active) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return types = [0, "perm", "temp", "soft"] @@ -351,9 +322,9 @@ class BanCog(CacheCog): for ban in bans: if not ban.username: user = await self.bot.fetch_user(ban.user) - ban.username = user.name if user else "[deleted user]" + ban.username = user.username if user else "[deleted user]" fields.append( - Field( + EmbedField( name=f"Username: {ban.username}#{ban.discrim}", value=( f"Date: {ban.created_at.strftime('%d-%m-%Y')}\n" @@ -370,8 +341,8 @@ class BanCog(CacheCog): for ban in bans: if ban.user.id not in db_bans: fields.append( - Field( - name=f"Username: {ban.user.name}#" + f"{ban.user.discriminator}", + EmbedField( + name=f"Username: {ban.user.username}#" + f"{ban.user.discriminator}", value=( f"Date: [unknown]\n" f"User ID: {ban.user.id}\n" @@ -395,26 +366,15 @@ class BanCog(CacheCog): description=f"No {'in' if not active else ''}active bans", fields=[], ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) else: for i in range(0, len(bans), 5): - embed = build_embed(title=title, description="", fields=fields[i : i + 5]) # noqa: E203 - embed.set_thumbnail(url=ctx.guild.icon_url) + embed = build_embed(title=title, description="", fields=fields[i : i + 5]) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "guild": ctx.guild.id, @@ -426,4 +386,4 @@ class BanCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 9c74910..623ddbb 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,53 +1,38 @@ """J.A.R.V.I.S. KickCog.""" -from discord import User -from discord.ext.commands import Bot -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +from dis_snek import InteractionContext, Permissions, Scale +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, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check from jarvis.db.models import Kick from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions -class KickCog(CacheCog): +class KickCog(Scale): """J.A.R.V.I.S. KickCog.""" - def __init__(self, bot: Bot): - super().__init__(bot) - - @cog_ext.cog_slash( - name="kick", - description="Kick a user", - options=[ - create_option( - name="user", - description="User to kick", - option_type=6, - required=True, - ), - create_option( - name="reason", - description="Kick reason", - required=False, - option_type=3, - ), - ], + @slash_command(name="kick", description="Kick a user") + @slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="reason", description="Kick reason", opt_type=OptionTypes.STRING, required=True ) - @admin_or_permissions(kick_members=True) - async def _kick(self, ctx: SlashContext, user: User, reason: str = None) -> None: + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None: if not user or user == ctx.author: - await ctx.send("You cannot kick yourself.", hidden=True) + await ctx.send("You cannot kick yourself.", ephemeral=True) return if user == self.bot.user: - await ctx.send("I'm afraid I can't let you do that", hidden=True) + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) return if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", hidden=True) + await ctx.send("Reason must be < 100 characters", ephemeral=True) return - if not reason: - reason = "Mr. Stark is displeased with your presence. Please leave." guild_name = ctx.guild.name embed = build_embed( title=f"You have been kicked from {guild_name}", @@ -56,10 +41,10 @@ class KickCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) send_failed = False try: @@ -68,19 +53,16 @@ class KickCog(CacheCog): send_failed = True await ctx.guild.kick(user, reason=reason) - fields = [Field(name="DM Sent?", value=str(not send_failed))] + fields = [EmbedField(name="DM Sent?", value=str(not send_failed))] embed = build_embed( title="User Kicked", description=f"Reason: {reason}", fields=fields, ) - embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, - ) - embed.set_thumbnail(url=user.avatar_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + 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) _ = Kick( diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 75c6427..6fe34b7 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,134 +1,113 @@ """J.A.R.V.I.S. LockCog.""" -from contextlib import suppress -from typing import Union +from dis_snek import Scale -from discord import Role, TextChannel, User, VoiceChannel -from discord.ext.commands import Bot -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option - -from jarvis.db.models import Lock -from jarvis.utils.cachecog import CacheCog -from jarvis.utils.permissions import admin_or_permissions +# TODO: Uncomment 99% of code once implementation is figured out +# from contextlib import suppress +# from typing import Union +# +# from dis_snek import InteractionContext, Scale, Snake +# from dis_snek.models.discord.enums import Permissions +# from dis_snek.models.discord.role import Role +# from dis_snek.models.discord.user import User +# from dis_snek.models.discord.channel import GuildText, GuildVoice, PermissionOverwrite +# from dis_snek.models.snek.application_commands import ( +# OptionTypes, +# PermissionTypes, +# slash_command, +# slash_option, +# ) +# from dis_snek.models.snek.command import check +# +# from jarvis.db.models import Lock +# from jarvis.utils.permissions import admin_or_permissions -class LockCog(CacheCog): +class LockCog(Scale): """J.A.R.V.I.S. LockCog.""" - def __init__(self, bot: Bot): - super().__init__(bot) - - async def _lock_channel( - self, - channel: Union[TextChannel, VoiceChannel], - role: Role, - admin: User, - reason: str, - allow_send: bool = False, - ) -> None: - overrides = channel.overwrites_for(role) - if isinstance(channel, TextChannel): - overrides.send_messages = allow_send - elif isinstance(channel, VoiceChannel): - overrides.speak = allow_send - await channel.set_permissions(role, overwrite=overrides, reason=reason) - - async def _unlock_channel( - self, - channel: Union[TextChannel, VoiceChannel], - role: Role, - admin: User, - ) -> None: - overrides = channel.overwrites_for(role) - if isinstance(channel, TextChannel): - overrides.send_messages = None - elif isinstance(channel, VoiceChannel): - overrides.speak = None - await channel.set_permissions(role, overwrite=overrides) - - @cog_ext.cog_slash( - name="lock", - description="Locks a channel", - options=[ - create_option( - name="reason", - description="Lock Reason", - option_type=3, - required=True, - ), - create_option( - name="duration", - description="Lock duration in minutes (default 10)", - option_type=4, - required=False, - ), - create_option( - name="channel", - description="Channel to lock", - option_type=7, - required=False, - ), - ], - ) - @admin_or_permissions(manage_channels=True) - async def _lock( - self, - ctx: SlashContext, - reason: str, - duration: int = 10, - channel: Union[TextChannel, VoiceChannel] = None, - ) -> None: - await ctx.defer(hidden=True) - if duration <= 0: - await ctx.send("Duration must be > 0", hidden=True) - return - elif duration >= 300: - await ctx.send("Duration must be < 5 hours", hidden=True) - return - if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", hidden=True) - return - if not channel: - channel = ctx.channel - for role in ctx.guild.roles: - with suppress(Exception): - await self._lock_channel(channel, role, ctx.author, reason) - _ = Lock( - channel=channel.id, - guild=ctx.guild.id, - admin=ctx.author.id, - reason=reason, - duration=duration, - ).save() - await ctx.send(f"{channel.mention} locked for {duration} minute(s)") - - @cog_ext.cog_slash( - name="unlock", - description="Unlocks a channel", - options=[ - create_option( - name="channel", - description="Channel to lock", - option_type=7, - required=False, - ), - ], - ) - @admin_or_permissions(manage_channels=True) - async def _unlock( - self, - ctx: SlashContext, - channel: Union[TextChannel, VoiceChannel] = 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.", hidden=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") + # @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/lockdown.py b/jarvis/cogs/admin/lockdown.py index dcf37ba..cd711fb 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -8,7 +8,8 @@ from discord_slash.utils.manage_commands import create_option from jarvis.db.models import Lock from jarvis.utils.cachecog import CacheCog -from jarvis.utils.permissions import admin_or_permissions + +# from jarvis.utils.permissions import admin_or_permissions class LockdownCog(CacheCog): @@ -21,34 +22,34 @@ class LockdownCog(CacheCog): base="lockdown", name="start", description="Locks a server", - options=[ + choices=[ create_option( name="reason", description="Lockdown Reason", - option_type=3, + opt_type=3, required=True, ), create_option( name="duration", description="Lockdown duration in minutes (default 10)", - option_type=4, + opt_type=4, required=False, ), ], ) - @admin_or_permissions(manage_channels=True) + # @check(admin_or_permissions(manage_channels=True)) async def _lockdown_start( self, ctx: SlashContext, reason: str, duration: int = 10, ) -> None: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) if duration <= 0: - await ctx.send("Duration must be > 0", hidden=True) + await ctx.send("Duration must be > 0", ephemeral=True) return elif duration >= 300: - await ctx.send("Duration must be < 5 hours", hidden=True) + await ctx.send("Duration must be < 5 hours", ephemeral=True) return channels = ctx.guild.channels roles = ctx.guild.roles @@ -87,7 +88,7 @@ class LockdownCog(CacheCog): update = False locks = Lock.objects(guild=ctx.guild.id, active=True) if not locks: - await ctx.send("No lockdown detected.", hidden=True) + await ctx.send("No lockdown detected.", ephemeral=True) return await ctx.defer() for channel in channels: diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 7bf851c..f34507d 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,132 +1,125 @@ """J.A.R.V.I.S. MuteCog.""" -from discord import Member -from discord.ext import commands -from discord.utils import get -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +from datetime import datetime -from jarvis.db.models import Mute, Setting +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.field import Field from jarvis.utils.permissions import admin_or_permissions -class MuteCog(commands.Cog): +class MuteCog(Scale): """J.A.R.V.I.S. MuteCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @cog_ext.cog_slash( - name="mute", - description="Mute a user", - options=[ - create_option( - name="user", - description="User to mute", - option_type=6, - required=True, - ), - create_option( - name="reason", - description="Reason for mute", - option_type=3, - required=True, - ), - create_option( - name="duration", - description="Duration of mute in minutes, default 30", - option_type=4, - required=False, - ), + @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), ], ) - @admin_or_permissions(mute_members=True) - async def _mute(self, ctx: SlashContext, user: Member, reason: str, duration: int = 30) -> None: + @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.", hidden=True) + 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", hidden=True) + 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", hidden=True) + await ctx.send("Reason must be < 100 characters", ephemeral=True) return - mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first() - if not mute_setting: - await ctx.send( - "Please configure a mute role with /settings mute first", - hidden=True, - ) + + # 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 - role = get(ctx.guild.roles, id=mute_setting.value) - if role in user.roles: - await ctx.send("User already muted", hidden=True) - return - await user.add_roles(role, reason=reason) - if duration < 0 or duration > 300: - duration = -1 + + await user.timeout(communication_disabled_until=duration, reason=reason) _ = Mute( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, - active=True if duration >= 0 else False, + active=True, ).save() embed = build_embed( title="User Muted", description=f"{user.mention} has been muted", - fields=[Field(name="Reason", value=reason)], + fields=[EmbedField(name="Reason", value=reason)], ) - embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, - ) - embed.set_thumbnail(url=user.avatar_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + 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) - @cog_ext.cog_slash( - name="unmute", - description="Unmute a user", - options=[ - create_option( - name="user", - description="User to unmute", - option_type=6, - required=True, - ) - ], + @slash_command(name="unmute", description="Unmute a user") + @slash_option( + name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True ) - @admin_or_permissions(mute_members=True) - async def _unmute(self, ctx: SlashContext, user: Member) -> None: - mute_setting = Setting.objects(guild=ctx.guild.id, setting="mute").first() - if not mute_setting: - await ctx.send( - "Please configure a mute role with /settings mute first.", - hidden=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 - role = get(ctx.guild.roles, id=mute_setting.value) - if role in user.roles: - await user.remove_roles(role, reason="Unmute") - else: - await ctx.send("User is not muted.", hidden=True) - return - - _ = Mute.objects(guild=ctx.guild.id, user=user.id).update(set__active=False) embed = build_embed( title="User Unmuted", description=f"{user.mention} has been unmuted", fields=[], ) - embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, - ) - embed.set_thumbnail(url=user.avatar_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + 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) + 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 adae1ac..e86cb1a 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,35 +1,34 @@ """J.A.R.V.I.S. PurgeCog.""" -from discord import TextChannel -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check from jarvis.db.models import Autopurge, Purge from jarvis.utils.permissions import admin_or_permissions -class PurgeCog(commands.Cog): +class PurgeCog(Scale): """J.A.R.V.I.S. PurgeCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @cog_ext.cog_slash( - name="purge", - description="Purge messages from channel", - options=[ - create_option( - name="amount", - description="Amount of messages to purge", - required=False, - option_type=4, - ) - ], + @slash_command(name="purge", description="Purge messages from channel") + @slash_option( + name="amount", + description="Amount of messages to purge, default 10", + opt_type=OptionTypes.INTEGER, + required=False, ) - @admin_or_permissions(manage_messages=True) - async def _purge(self, ctx: SlashContext, amount: int = 10) -> None: + @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) + async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None: if amount < 1: - await ctx.send("Amount must be >= 1", hidden=True) + await ctx.send("Amount must be >= 1", ephemeral=True) return await ctx.defer() channel = ctx.channel @@ -44,39 +43,37 @@ class PurgeCog(commands.Cog): count=amount, ).save() - @cog_ext.cog_subcommand( - base="autopurge", - name="add", - description="Automatically purge messages after x seconds", - options=[ - create_option( - name="channel", - description="Channel to autopurge", - option_type=7, - required=True, - ), - create_option( - name="delay", - description="Seconds to keep message before purge, default 30", - option_type=4, - required=False, - ), - ], + @slash_command( + name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages" ) - @admin_or_permissions(manage_messages=True) - async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", hidden=True) + @slash_option( + name="channel", + description="Channel to autopurge", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="delay", + description="Seconds to keep message before purge, default 30", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) + async def _autopurge_add( + self, ctx: InteractionContext, channel: GuildText, delay: int = 30 + ) -> None: + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText channel", ephemeral=True) return if delay <= 0: - await ctx.send("Delay must be > 0", hidden=True) + await ctx.send("Delay must be > 0", ephemeral=True) return elif delay > 300: - await ctx.send("Delay must be < 5 minutes", hidden=True) + 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.", hidden=True) + await ctx.send("Autopurge already exists.", ephemeral=True) return _ = Autopurge( guild=ctx.guild.id, @@ -86,52 +83,48 @@ class PurgeCog(commands.Cog): ).save() await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") - @cog_ext.cog_subcommand( - base="autopurge", - name="remove", - description="Remove an autopurge", - options=[ - create_option( - name="channel", - description="Channel to remove from autopurge", - option_type=7, - required=True, - ), - ], + @slash_command( + name="autopurge", sub_cmd_name="remove", sub_cmd_description="Remove an autopurge" ) - @admin_or_permissions(manage_messages=True) - async def _autopurge_remove(self, ctx: SlashContext, channel: TextChannel) -> None: + @slash_option( + name="channel", + description="Channel to remove from autopurge", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @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) if not autopurge: - await ctx.send("Autopurge does not exist.", hidden=True) + await ctx.send("Autopurge does not exist.", ephemeral=True) return autopurge.delete() await ctx.send(f"Autopurge removed from {channel.mention}.") - @cog_ext.cog_subcommand( - base="autopurge", - name="update", - description="Update autopurge on a channel", - options=[ - create_option( - name="channel", - description="Channel to update", - option_type=7, - required=True, - ), - create_option( - name="delay", - description="New time to save", - option_type=4, - required=True, - ), - ], + @slash_command( + name="autopurge", + sub_cmd_name="update", + sub_cmd_description="Update autopurge on a channel", ) - @admin_or_permissions(manage_messages=True) - async def _autopurge_update(self, ctx: SlashContext, channel: TextChannel, delay: int) -> None: + @slash_option( + name="channel", + description="Channel to update", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="delay", + description="New time to save", + opt_type=OptionTypes.INTEGER, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) + async def _autopurge_update( + self, ctx: InteractionContext, channel: GuildText, delay: int + ) -> None: autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) if not autopurge: - await ctx.send("Autopurge does not exist.", hidden=True) + await ctx.send("Autopurge does not exist.", ephemeral=True) return autopurge.delay = delay autopurge.save() diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 46ee2c5..6a01ede 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,44 +1,39 @@ """J.A.R.V.I.S. RolepingCog.""" from datetime import datetime, timedelta -from ButtonPaginator import Paginator -from discord import Member, Role -from discord.ext.commands import Bot -from discord_slash import SlashContext, cog_ext -from discord_slash.model import ButtonStyle -from discord_slash.utils.manage_commands import create_option +from dis_snek import InteractionContext, Permissions, Snake +from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.role import Role +from dis_snek.models.discord.user import Member +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +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.field import Field from jarvis.utils.permissions import admin_or_permissions class RolepingCog(CacheCog): """J.A.R.V.I.S. RolepingCog.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snake): super().__init__(bot) - @cog_ext.cog_subcommand( - base="roleping", - name="add", - description="Add a role to roleping", - options=[ - create_option( - name="role", - description="Role to add to roleping", - option_type=8, - required=True, - ) - ], + @slash_command( + name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" ) - @admin_or_permissions(manage_guild=True) - async def _roleping_add(self, ctx: SlashContext, role: Role) -> None: + @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() if roleping: - await ctx.send(f"Role `{role.name}` already in roleping.", hidden=True) + await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return _ = Roleping( role=role.id, @@ -49,55 +44,45 @@ class RolepingCog(CacheCog): ).save() await ctx.send(f"Role `{role.name}` added to roleping.") - @cog_ext.cog_subcommand( - base="roleping", - name="remove", - description="Remove a role from the roleping", - options=[ - create_option( - name="role", - description="Role to remove from roleping", - option_type=8, - required=True, - ) - ], + @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @slash_option( + name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) - @admin_or_permissions(manage_guild=True) - async def _roleping_remove(self, ctx: SlashContext, role: Role) -> None: + @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) if not roleping: - await ctx.send("Roleping does not exist", hidden=True) + await ctx.send("Roleping does not exist", ephemeral=True) return roleping.delete() await ctx.send(f"Role `{role.name}` removed from roleping.") - @cog_ext.cog_subcommand( - base="roleping", - name="list", - description="List all blocklisted roles", - ) - async def _roleping_list(self, ctx: SlashContext) -> None: + @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(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return rolepings = Roleping.objects(guild=ctx.guild.id) if not rolepings: - await ctx.send("No rolepings configured", hidden=True) + await ctx.send("No rolepings configured", ephemeral=True) return embeds = [] for roleping in rolepings: - role = ctx.guild.get_role(roleping.role) + 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] - bypass_users = [ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"]] + bypass_users = [ + await ctx.guild.get_member(u).mention or "||`[redacted]`||" + for u in roleping.bypass["users"] + ] bypass_roles = bypass_roles or ["None"] bypass_users = bypass_users or ["None"] embed = build_embed( @@ -105,44 +90,33 @@ class RolepingCog(CacheCog): description=role.mention, color=str(role.color), fields=[ - Field( + EmbedField( name="Created At", value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"), inline=False, ), - Field(name="Active", value=str(roleping.active)), - Field( + EmbedField(name="Active", value=str(roleping.active)), + EmbedField( name="Bypass Users", value="\n".join(bypass_users), ), - Field( + EmbedField( name="Bypass Roles", value="\n".join(bypass_roles), ), ], ) - admin = ctx.guild.get_member(roleping.admin) + admin = await ctx.guild.get_member(roleping.admin) if not admin: admin = self.bot.user - embed.set_author(name=admin.nick or admin.name, icon_url=admin.avatar_url) + embed.set_author(name=admin.display_name, icon_url=admin.display_avatar.url) embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}") embeds.append(embed) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=embeds, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(embeds) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300) self.cache[hash(paginator)] = { "user": ctx.author.id, @@ -152,45 +126,37 @@ class RolepingCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) - @cog_ext.cog_subcommand( - base="roleping", - subcommand_group="bypass", - name="user", - description="Add a user as a bypass to a roleping", - base_desc="Block roles from being pinged", - sub_group_desc="Allow specific users/roles to ping rolepings", - options=[ - create_option( - name="user", - description="User to add", - option_type=6, - required=True, - ), - create_option( - name="rping", - description="Rolepinged role", - option_type=8, - required=True, - ), - ], + @slash_command( + name="roleping", + description="Block roles from being pinged", + group_name="bypass", + group_description="Allow specific users/roles to ping rolepings", + sub_cmd_name="user", + sub_cmd_description="Add a user as a bypass to a roleping", ) - @admin_or_permissions(manage_guild=True) - async def _roleping_bypass_user(self, ctx: SlashContext, user: Member, rping: Role) -> None: + @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 + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _roleping_bypass_user( + self, ctx: InteractionContext, user: Member, rping: Role + ) -> None: roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) + await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return if user.id in roleping.bypass["users"]: - await ctx.send(f"{user.mention} already in bypass", hidden=True) + await ctx.send(f"{user.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["users"]) == 10: await ctx.send( "Already have 10 users in bypass. Please consider using roles for roleping bypass", - hidden=True, + ephemeral=True, ) return @@ -199,51 +165,40 @@ class RolepingCog(CacheCog): if matching_role: await ctx.send( f"{user.mention} already has bypass via {matching_role[0].mention}", - hidden=True, + ephemeral=True, ) return roleping.bypass["users"].append(user.id) roleping.save() - await ctx.send(f"{user.nick or user.name} user bypass added for `{rping.name}`") + await ctx.send(f"{user.display_name} user bypass added for `{rping.name}`") - @cog_ext.cog_subcommand( - base="roleping", - subcommand_group="bypass", - name="role", - description="Add a role as a bypass to a roleping", - base_desc="Block roles from being pinged", - sub_group_desc="Allow specific users/roles to ping rolepings", - options=[ - create_option( - name="role", - description="Role to add", - option_type=8, - required=True, - ), - create_option( - name="rping", - description="Rolepinged role", - option_type=8, - required=True, - ), - ], + @slash_command( + name="roleping", + group_name="bypass", + sub_cmd_name="role", + description="Add a role as a bypass to roleping", ) - @admin_or_permissions(manage_guild=True) - async def _roleping_bypass_role(self, ctx: SlashContext, role: Role, rping: Role) -> None: + @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 + ) + @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() if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) + await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", hidden=True) + await ctx.send(f"{role.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["roles"]) == 10: await ctx.send( - "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass", - hidden=True, + "Already have 10 roles in bypass. " + "Please consider consolidating roles for roleping bypass", + ephemeral=True, ) return @@ -251,80 +206,67 @@ class RolepingCog(CacheCog): roleping.save() await ctx.send(f"{role.name} role bypass added for `{rping.name}`") - @cog_ext.cog_subcommand( - base="roleping", - subcommand_group="restore", - name="user", - description="Remove a role bypass", - base_desc="Block roles from being pinged", - sub_group_desc="Remove a bypass from a roleping (restoring it)", - options=[ - create_option( - name="user", - description="User to add", - option_type=6, - required=True, - ), - create_option( - name="rping", - description="Rolepinged role", - option_type=8, - required=True, - ), - ], + @slash_command( + name="roleping", + description="Block roles from being pinged", + group_name="restore", + group_description="Remove a roleping bypass", + sub_cmd_name="user", + sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) - @admin_or_permissions(manage_guild=True) - async def _roleping_restore_user(self, ctx: SlashContext, user: Member, rping: Role) -> None: + @slash_option( + name="user", description="User to remove", opt_type=OptionTypes.USER, required=True + ) + @slash_option( + name="rping", 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 + ) -> None: roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) + await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return if user.id not in roleping.bypass["users"]: - await ctx.send(f"{user.mention} not in bypass", hidden=True) + await ctx.send(f"{user.mention} not in bypass", ephemeral=True) return roleping.bypass["users"].delete(user.id) roleping.save() - await ctx.send(f"{user.nick or user.name} user bypass removed for `{rping.name}`") + await ctx.send(f"{user.display_name} user bypass removed for `{rping.name}`") - @cog_ext.cog_subcommand( - base="roleping", - subcommand_group="restore", - name="role", - description="Remove a role bypass", - base_desc="Block roles from being pinged", - sub_group_desc="Remove a bypass from a roleping (restoring it)", - options=[ - create_option( - name="role", - description="Role to add", - option_type=8, - required=True, - ), - create_option( - name="rping", - description="Rolepinged role", - option_type=8, - required=True, - ), - ], + @slash_command( + name="roleping", + group_name="restore", + sub_cmd_name="role", + description="Remove a bypass from a roleping (restoring it)", ) - @admin_or_permissions(manage_guild=True) - async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None: + @slash_option( + name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + ) + @check(admin_or_permissions(manage_guild=True)) + async def _roleping_restore_role( + self, ctx: InteractionContext, role: Role, rping: Role + ) -> None: roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", hidden=True) + await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", hidden=True) + await ctx.send(f"{role.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["roles"]) == 10: await ctx.send( - "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass", - hidden=True, + "Already have 10 roles in bypass. " + "Please consider consolidating roles for roleping bypass", + ephemeral=True, ) return diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index ec18e9a..edd0db5 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,12 +1,16 @@ """J.A.R.V.I.S. WarningCog.""" from datetime import datetime, timedelta -from ButtonPaginator import Paginator -from discord import User -from discord.ext.commands import Bot -from discord_slash import SlashContext, cog_ext -from discord_slash.model import ButtonStyle -from discord_slash.utils.manage_commands import create_choice, create_option +from dis_snek import InteractionContext, Permissions, Snake +from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.user import User +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 Warning from jarvis.utils import build_embed @@ -18,43 +22,35 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(CacheCog): """J.A.R.V.I.S. WarningCog.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snake): super().__init__(bot) - @cog_ext.cog_slash( - name="warn", - description="Warn a user", - options=[ - create_option( - name="user", - description="User to warn", - option_type=6, - required=True, - ), - create_option( - name="reason", - description="Reason for warning", - option_type=3, - required=True, - ), - create_option( - name="duration", - description="Duration of warning in hours, default 24", - option_type=4, - required=False, - ), - ], + @slash_command(name="warn", description="Warn a user") + @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="reason", + description="Reason for warning", + opt_type=OptionTypes.STRING, + required=True, ) - @admin_or_permissions(manage_guild=True) - async def _warn(self, ctx: SlashContext, user: User, reason: str, duration: int = 24) -> None: + @slash_option( + name="duration", + description="Duration of warning in hours, default 24", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _warn( + self, ctx: InteractionContext, user: User, reason: str, duration: int = 24 + ) -> None: if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", hidden=True) + await ctx.send("Reason must be < 100 characters", ephemeral=True) return if duration <= 0: - await ctx.send("Duration must be > 0", hidden=True) + await ctx.send("Duration must be > 0", ephemeral=True) return elif duration >= 120: - await ctx.send("Duration must be < 5 days", hidden=True) + await ctx.send("Duration must be < 5 days", ephemeral=True) return await ctx.defer() _ = Warning( @@ -71,52 +67,41 @@ class WarningCog(CacheCog): description=f"{user.mention} has been warned", fields=fields, ) - embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, - ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="warnings", - description="Get count of user warnings", - options=[ - create_option( - name="user", - description="User to view", - option_type=6, - required=True, - ), - create_option( - name="active", - description="View only active", - option_type=4, - required=False, - choices=[ - create_choice(name="Yes", value=1), - create_choice(name="No", value=0), - ], - ), + @slash_command(name="warnings", description="Get count of user warnings") + @slash_option(name="user", description="User to view", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="active", + description="View active only", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[ + SlashCommandChoice(name="Yes", value=1), + SlashCommandChoice(name="No", value=0), ], ) - @admin_or_permissions(manage_guild=True) - async def _warnings(self, ctx: SlashContext, user: User, active: bool = 1) -> None: + @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(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return 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") + active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by( + "-created_at" + ) pages = [] if active: @@ -126,16 +111,16 @@ class WarningCog(CacheCog): description=f"{warnings.count()} total | 0 currently active", fields=[], ) - embed.set_author(name=user.name, icon_url=user.avatar_url) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_author(name=user.username, icon_url=user.display_avatar.url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) else: fields = [] for warn in active_warns: - admin = ctx.guild.get_member(warn.admin) + admin = await ctx.guild.get_member(warn.admin) admin_name = "||`[redacted]`||" if admin: - admin_name = f"{admin.name}#{admin.discriminator}" + admin_name = f"{admin.username}#{admin.discriminator}" fields.append( Field( name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"), @@ -146,15 +131,17 @@ 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", - fields=fields[i : i + 5], # noqa: E203 + description=( + f"{warnings.count()} total | {active_warns.count()} currently active" + ), + fields=fields[i : i + 5], ) embed.set_author( - name=user.name + "#" + user.discriminator, - icon_url=user.avatar_url, + name=user.username + "#" + user.discriminator, + icon_url=user.display_avatar.url, ) - embed.set_thumbnail(url=ctx.guild.icon_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + embed.set_thumbnail(url=ctx.guild.icon.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") pages.append(embed) else: fields = [] @@ -171,28 +158,18 @@ 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", - fields=fields[i : i + 5], # noqa: E203 + description=( + f"{warnings.count()} total | {active_warns.count()} currently active" + ), + fields=fields[i : i + 5], ) embed.set_author( - name=user.name + "#" + user.discriminator, - icon_url=user.avatar_url, + name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator(bot=self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "guild": ctx.guild.id, @@ -204,4 +181,4 @@ class WarningCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 378f705..78d403b 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,46 +1,45 @@ """J.A.R.V.I.S. Autoreact Cog.""" import re +from typing import Optional, Tuple -from discord import TextChannel -from discord.ext import commands -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check 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 -class AutoReactCog(commands.Cog): +class AutoReactCog(Scale): """J.A.R.V.I.S. Autoreact Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") - @cog_ext.cog_subcommand( - base="autoreact", - name="create", - description="Add an autoreact to a channel", - options=[ - create_option( - name="channel", - description="Channel to monitor", - option_type=7, - required=True, - ) - ], - ) - @admin_or_permissions(manage_guild=True) - async def _autoreact_create(self, ctx: SlashContext, channel: TextChannel) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a text channel", hidden=True) - return + async def create_autoreact( + self, ctx: InteractionContext, channel: GuildText + ) -> Tuple[bool, Optional[str]]: + """ + Create an autoreact monitor on a channel. + + Args: + ctx: Interaction context of command + channel: Channel to monitor + + Returns: + Tuple of success? and error message + """ exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() if exists: - await ctx.send(f"Autoreact already exists for {channel.mention}.", hidden=True) - return + return False, f"Autoreact already exists for {channel.mention}." _ = Autoreact( guild=ctx.guild.id, @@ -48,152 +47,145 @@ class AutoReactCog(commands.Cog): reactions=[], admin=ctx.author.id, ).save() - await ctx.send(f"Autoreact created for {channel.mention}!") - @cog_ext.cog_subcommand( - base="autoreact", - name="delete", - description="Delete an autoreact from a channel", - options=[ - create_option( - name="channel", - description="Channel to stop monitoring", - option_type=7, - required=True, - ) - ], - ) - @admin_or_permissions(manage_guild=True) - async def _autoreact_delete(self, ctx: SlashContext, channel: TextChannel) -> None: - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() - if exists: - await ctx.send(f"Autoreact removed from {channel.mention}") - else: - await ctx.send(f"Autoreact not found on {channel.mention}", hidden=True) + return True, None - @cog_ext.cog_subcommand( - base="autoreact", - name="add", - description="Add an autoreact emote to an existing autoreact", - options=[ - create_option( - name="channel", - description="Autoreact channel to add emote to", - option_type=7, - required=True, - ), - create_option( - name="emote", - description="Emote to add", - option_type=3, - required=True, - ), - ], + async def delete_autoreact(self, ctx: InteractionContext, channel: GuildText) -> bool: + """ + Remove an autoreact monitor on a channel. + + Args: + ctx: Interaction context of command + channel: Channel to stop monitoring + + Returns: + Success? + """ + return Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() is not None + + @slash_command( + name="autoreact", + sub_cmd_name="add", + sub_cmd_description="Add an autoreact emote to a channel", ) - @admin_or_permissions(manage_guild=True) - async def _autoreact_add(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None: + @slash_option( + name="channel", + description="Autoreact channel to add emote to", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None: await ctx.defer() custom_emoji = self.custom_emote.match(emote) standard_emoji = emote in emoji_list if not custom_emoji and not standard_emoji: await ctx.send( "Please use either an emote from this server or a unicode emoji.", - hidden=True, + ephemeral=True, ) return if custom_emoji: emoji_id = int(custom_emoji.group(1)) if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): - await ctx.send("Please use a custom emote from this server.", hidden=True) + await ctx.send("Please use a custom emote from this server.", ephemeral=True) return - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() - if not exists: - await ctx.send(f"Please create autoreact first with /autoreact create {channel.mention}") - return - if emote in exists.reactions: + autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + if not autoreact: + self.create_autoreact(ctx, channel) + autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + if emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", - hidden=True, + ephemeral=True, ) return - if len(exists.reactions) >= 5: + if len(autoreact.reactions) >= 5: await ctx.send( "Max number of reactions hit. Remove a different one to add this one", - hidden=True, + ephemeral=True, ) return - exists.reactions.append(emote) - exists.save() + autoreact.reactions.append(emote) + autoreact.save() await ctx.send(f"Added {emote} to {channel.mention} autoreact.") - @cog_ext.cog_subcommand( - base="autoreact", - name="remove", - description="Remove an autoreact emote from an existing autoreact", - options=[ - create_option( - name="channel", - description="Autoreact channel to remove emote from", - option_type=7, - required=True, - ), - create_option( - name="emote", - description="Emote to remove", - option_type=3, - required=True, - ), - ], + @slash_command( + name="autoreact", + sub_cmd_name="remove", + sub_cmd_description="Remove an autoreact emote to a channel", ) - @admin_or_permissions(manage_guild=True) - async def _autoreact_remove(self, ctx: SlashContext, channel: TextChannel, emote: str) -> None: - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() - if not exists: + @slash_option( + name="channel", + description="Autoreact channel to remove emote from", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="emote", + description="Emote to remove (use all to delete)", + opt_type=OptionTypes.STRING, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _autoreact_remove( + self, ctx: InteractionContext, channel: GuildText, emote: str + ) -> None: + autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + if not autoreact: await ctx.send( - f"Please create autoreact first with /autoreact create {channel.mention}", - hidden=True, + f"Please create autoreact first with /autoreact add {channel.mention} {emote}", + ephemeral=True, ) return - if emote not in exists.reactions: + if emote.lower() == "all": + self.delete_autoreact(ctx, channel) + await ctx.send(f"Autoreact removed from {channel.mention}") + elif emote not in autoreact.reactions: await ctx.send( f"{emote} not used in {channel.mention} autoreactions.", - hidden=True, + ephemeral=True, ) return - exists.reactions.remove(emote) - exists.save() - await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") + else: + autoreact.reactions.remove(emote) + autoreact.save() + if len(autoreact.reactions) == 0: + self.delete_autoreact(ctx, channel) + await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") - @cog_ext.cog_subcommand( - base="autoreact", - name="list", - description="List all autoreacts on a channel", - options=[ - create_option( - name="channel", - description="Autoreact channel to list", - option_type=7, - required=True, - ), - ], + @slash_command( + name="autoreact", + sub_cmd_name="list", + sub_cmd_description="List all autoreacts on a channel", ) - @admin_or_permissions(manage_guild=True) - async def _autoreact_list(self, ctx: SlashContext, channel: TextChannel) -> None: + @slash_option( + name="channel", + description="Autoreact channel to list", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None: exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() if not exists: await ctx.send( - f"Please create autoreact first with /autoreact create {channel.mention}", - hidden=True, + f"Please create autoreact first with /autoreact add {channel.mention} ", + ephemeral=True, ) return message = "" if len(exists.reactions) > 0: - message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join(exists.reactions) + message = f"Current active autoreacts on {channel.mention}:\n" + "\n".join( + exists.reactions + ) else: message = f"No reactions set on {channel.mention}" await ctx.send(message) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add AutoReactCog to J.A.R.V.I.S.""" - bot.add_cog(AutoReactCog(bot)) + AutoReactCog(bot) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index b236e69..d509f0f 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -3,22 +3,23 @@ import re from datetime import datetime, timedelta import aiohttp -from ButtonPaginator import Paginator -from discord import Member, User -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import ButtonStyle +from dis_snek import InteractionContext, Snake +from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.user import Member, User +from dis_snek.models.snek.application_commands import slash_command +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.db.models import Guess from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", + r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) @@ -26,7 +27,7 @@ invites = re.compile( class CTCCog(CacheCog): """J.A.R.V.I.S. Complete the Code 2 Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): super().__init__(bot) self._session = aiohttp.ClientSession() self.url = "https://completethecodetwo.cards/pw" @@ -34,45 +35,48 @@ class CTCCog(CacheCog): def __del__(self): self._session.close() - @cog_ext.cog_subcommand( - base="ctc2", - name="about", - description="CTC2 related commands", - guild_ids=guild_ids, + @slash_command( + name="ctc2", sub_cmd_name="about", description="CTC2 related commands", scopes=guild_ids ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _about(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _about(self, ctx: InteractionContext) -> None: await ctx.send("See https://completethecode.com for more information") - @cog_ext.cog_subcommand( - base="ctc2", - name="pw", - description="Guess a password for https://completethecodetwo.cards", - guild_ids=guild_ids, + @slash_command( + name="ctc2", + sub_cmd_name="pw", + sub_cmd_description="Guess a password for https://completethecodetwo.cards", + scopes=guild_ids, ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _pw(self, ctx: SlashContext, guess: str) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _pw(self, ctx: InteractionContext, guess: str) -> None: if len(guess) > 800: await ctx.send( - "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.", - hidden=True, + ( + "Listen here, dipshit. Don't be like <@256110768724901889>. " + "Make your guesses < 800 characters." + ), + ephemeral=True, ) return elif not valid.fullmatch(guess): await ctx.send( - "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses *readable*.", - hidden=True, + ( + "Listen here, dipshit. Don't be like <@256110768724901889>. " + "Make your guesses *readable*." + ), + ephemeral=True, ) return elif invites.search(guess): await ctx.send( "Listen here, dipshit. No using this to bypass sending invite links.", - hidden=True, + ephemeral=True, ) return guessed = Guess.objects(guess=guess).first() if guessed: - await ctx.send("Already guessed, dipshit.", hidden=True) + await ctx.send("Already guessed, dipshit.", ephemeral=True) return result = await self._session.post(self.url, data=guess) correct = False @@ -80,30 +84,30 @@ class CTCCog(CacheCog): await ctx.send(f"{ctx.author.mention} got it! Password is {guess}!") correct = True else: - await ctx.send("Nope.", hidden=True) + await ctx.send("Nope.", ephemeral=True) _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() - @cog_ext.cog_subcommand( - base="ctc2", - name="guesses", - description="Show guesses made for https://completethecodetwo.cards", - guild_ids=guild_ids, + @slash_command( + name="ctc2", + sub_cmd_name="guesses", + sub_cmd_description="Show guesses made for https://completethecodetwo.cards", + scopes=guild_ids, ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _guesses(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _guesses(self, ctx: InteractionContext) -> None: exists = self.check_cache(ctx) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return guesses = Guess.objects().order_by("-correct", "-id") fields = [] for guess in guesses: - user = ctx.guild.get_member(guess["user"]) + user = await ctx.guild.get_member(guess["user"]) if not user: user = await self.bot.fetch_user(guess["user"]) if not user: @@ -113,7 +117,7 @@ class CTCCog(CacheCog): name = "Correctly" if guess["correct"] else "Incorrectly" name += " guessed by: " + user fields.append( - Field( + EmbedField( name=name, value=guess["guess"] + "\n\u200b", inline=False, @@ -124,7 +128,7 @@ class CTCCog(CacheCog): embed = build_embed( title="completethecodetwo.cards guesses", description=f"{len(fields)} guesses so far", - fields=fields[i : i + 5], # noqa: E203 + fields=fields[i : i + 5], url="https://completethecodetwo.cards", ) embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png") @@ -134,18 +138,7 @@ class CTCCog(CacheCog): ) pages.append(embed) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - timeout=60 * 5, # 5 minute timeout - only=ctx.author, - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "guild": ctx.guild.id, @@ -155,9 +148,9 @@ class CTCCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add CTCCog to J.A.R.V.I.S.""" - bot.add_cog(CTCCog(bot)) + CTCCog(bot) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 79e4904..9a14251 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -2,26 +2,31 @@ import re import aiohttp -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option +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.config import get_config from jarvis.data.dbrand import shipping_lookup from jarvis.utils import build_embed -from jarvis.utils.field import Field guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] -class DbrandCog(commands.Cog): +class DbrandCog(Scale): """ dbrand functions for J.A.R.V.I.S. Mostly support functions. Credit @cpixl for the shipping API """ - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot self.base_url = "https://dbrand.com/" self._session = aiohttp.ClientSession() @@ -32,134 +37,130 @@ class DbrandCog(commands.Cog): def __del__(self): self._session.close() - @cog_ext.cog_subcommand( - base="db", - name="skin", - guild_ids=guild_ids, - description="See what skins are available", + @slash_command( + name="db", + sub_cmd_name="skin", + scopes=guild_ids, + sub_cmd_description="See what skins are available", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _skin(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _skin(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "/skins") - @cog_ext.cog_subcommand( - base="db", - name="robotcamo", - guild_ids=guild_ids, - description="Get some robot camo. Make Tony Stark proud", + @slash_command( + name="db", + sub_cmd_name="robotcamo", + scopes=guild_ids, + sub_cmd_description="Get some robot camo. Make Tony Stark proud", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _camo(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _camo(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "robot-camo") - @cog_ext.cog_subcommand( - base="db", - name="grip", - guild_ids=guild_ids, - description="See devices with Grip support", + @slash_command( + name="db", + sub_cmd_name="grip", + scopes=guild_ids, + sub_cmd_description="See devices with Grip support", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _grip(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _grip(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "grip") - @cog_ext.cog_subcommand( - base="db", - name="contact", - guild_ids=guild_ids, - description="Contact support", + @slash_command( + name="db", + sub_cmd_name="contact", + scopes=guild_ids, + sub_cmd_description="Contact support", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _contact(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _contact(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @cog_ext.cog_subcommand( - base="db", - name="support", - guild_ids=guild_ids, - description="Contact support", + @slash_command( + name="db", + sub_cmd_name="support", + scopes=guild_ids, + sub_cmd_description="Contact support", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _support(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _support(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @cog_ext.cog_subcommand( - base="db", - name="orderstat", - guild_ids=guild_ids, - description="Get your order status", + @slash_command( + name="db", + sub_cmd_name="orderstat", + scopes=guild_ids, + sub_cmd_description="Get your order status", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _orderstat(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _orderstat(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "order-status") - @cog_ext.cog_subcommand( - base="db", - name="orders", - guild_ids=guild_ids, - description="Get your order status", + @slash_command( + name="db", + sub_cmd_name="orders", + scopes=guild_ids, + sub_cmd_description="Get your order status", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _orders(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _orders(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "order-status") - @cog_ext.cog_subcommand( - base="db", - name="status", - guild_ids=guild_ids, - description="dbrand status", + @slash_command( + name="db", + sub_cmd_name="status", + scopes=guild_ids, + sub_cmd_description="dbrand status", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _status(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _status(self, ctx: InteractionContext) -> None: await ctx.send(self.base_url + "status") - @cog_ext.cog_subcommand( - base="db", - name="buy", - guild_ids=guild_ids, - description="Give us your money!", + @slash_command( + name="db", + sub_cmd_name="buy", + scopes=guild_ids, + sub_cmd_description="Give us your money!", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _buy(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _buy(self, ctx: InteractionContext) -> None: await ctx.send("Give us your money! " + self.base_url + "shop") - @cog_ext.cog_subcommand( - base="db", - name="extortion", - guild_ids=guild_ids, - description="(not) extortion", + @slash_command( + name="db", + sub_cmd_name="extortion", + scopes=guild_ids, + sub_cmd_description="(not) extortion", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _extort(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _extort(self, ctx: InteractionContext) -> None: await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion") - @cog_ext.cog_subcommand( - base="db", - name="wallpapers", - description="Robot Camo Wallpapers", - guild_ids=guild_ids, + @slash_command( + name="db", + sub_cmd_name="wallpapers", + sub_cmd_description="Robot Camo Wallpapers", + scopes=guild_ids, ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _wallpapers(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=30) + async def _wallpapers(self, ctx: InteractionContext) -> None: await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers") - @cog_ext.cog_subcommand( - base="db", - name="ship", - description="Get shipping information for your country", - guild_ids=guild_ids, - options=[ - ( - create_option( - name="search", - description="Country search query (2 character code, country name, emoji)", - option_type=3, - required=True, - ) - ) - ], + @slash_command( + name="db", + sub_cmd_name="ship", + sub_cmd_description="Get shipping information for your country", + scopes=guild_ids, ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _shipping(self, ctx: SlashContext, search: str) -> None: + @slash_option( + name="search", + description="Country search query (2 character code, country name, flag emoji)", + opt_type=OptionTypes.STRING, + required=True, + ) + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _shipping(self, ctx: InteractionContext, search: str) -> None: await ctx.defer() if not re.match(r"^[A-Z- ]+$", search, re.IGNORECASE): if re.match( @@ -173,7 +174,6 @@ class DbrandCog(commands.Cog): elif search == "🏳️": search = "fr" else: - print(search) await ctx.send("Please use text to search for shipping.") return if len(search) > 2: @@ -193,14 +193,14 @@ class DbrandCog(commands.Cog): fields = None if data is not None and data["is_valid"] and data["shipping_available"]: fields = [] - fields.append(Field(data["short-name"], data["time-title"])) + fields.append(EmbedField(data["short-name"], data["time-title"])) for service in data["shipping_services_available"][1:]: service_data = await self._session.get(self.api_url + dest + "/" + service["url"]) if service_data.status > 400: continue service_data = await service_data.json() fields.append( - Field( + EmbedField( service_data["short-name"], service_data["time-title"], ) @@ -215,7 +215,7 @@ class DbrandCog(commands.Cog): ) embed = build_embed( title="Shipping to {}".format(data["country"]), - description=description, + sub_cmd_description=description, color="#FFBB00", fields=fields, url=self.base_url + "shipping/" + country, @@ -229,8 +229,9 @@ class DbrandCog(commands.Cog): elif not data["is_valid"]: embed = build_embed( title="Check Shipping Times", - description=( - "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)" + sub_cmd_description=( + "Country not found.\nYou can [view all shipping " + "destinations here](https://dbrand.com/shipping)" ), fields=[], url="https://dbrand.com/shipping", @@ -245,7 +246,7 @@ class DbrandCog(commands.Cog): elif not data["shipping_available"]: embed = build_embed( title="Shipping to {}".format(data["country"]), - description=( + sub_cmd_description=( "No shipping available.\nTime to move to a country" " that has shipping available.\nYou can [find a new country " "to live in here](https://dbrand.com/shipping)" @@ -262,6 +263,6 @@ class DbrandCog(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add dbrandcog to J.A.R.V.I.S.""" - bot.add_cog(DbrandCog(bot)) + DbrandCog(bot) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 4345c0c..98f2db2 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -8,19 +8,27 @@ from typing import Any, Union import ulid as ulidpy from bson import ObjectId -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_choice, create_option +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.utils import build_embed, convert_bytesize -from jarvis.utils.field import Field supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x} OID_VERIFY = re.compile(r"^([1-9][0-9]{0,3}|0)(\.([1-9][0-9]{0,3}|0)){5,13}$") -URL_VERIFY = re.compile(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+") +URL_VERIFY = re.compile( + r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" +) DN_VERIFY = re.compile( - r"^(?:(?PCN=(?P[^,]*)),)?(?:(?P(?:(?:CN|OU)=[^,]+,?)+),)?(?P(?:DC=[^,]+,?)+)$" + r"^(?:(?PCN=(?P[^,]*)),)?(?:(?P(?:(?:CN|OU)=[^,]+,?)+),)?(?P(?:DC=[^,]+,?)+)$" # noqa: E501 ) ULID_VERIFY = re.compile(r"^[0-9a-z]{26}$", re.IGNORECASE) UUID_VERIFY = re.compile( @@ -29,7 +37,7 @@ UUID_VERIFY = re.compile( ) invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", + r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) @@ -47,43 +55,35 @@ def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str: BSIZE = 65536 block_idx = 0 while block_idx * BSIZE < len(data): - block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] # noqa: E203 + block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] hash.update(block) block_idx += 1 return hash.hexdigest() -class DevCog(commands.Cog): +class DevCog(Scale): """J.A.R.V.I.S. Developer Cog.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - - @cog_ext.cog_slash( - name="hash", - description="Hash some data", - options=[ - create_option( - name="method", - description="Hash method", - option_type=3, - required=True, - choices=[create_choice(name=x, value=x) for x in supported_hashes], - ), - create_option( - name="data", - description="Data to hash", - option_type=3, - required=True, - ), - ], + @slash_command(name="hash", description="Hash some data") + @slash_option( + name="method", + description="Hash method", + opt_type=OptionTypes.STRING, + required=True, + choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes], ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _hash(self, ctx: SlashContext, method: str, data: str) -> None: + @slash_option( + name="data", + description="Data to hash", + opt_type=OptionTypes.STRING, + required=True, + ) + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _hash(self, ctx: InteractionContext, method: str, data: str) -> None: if not data: await ctx.send( "No data to hash", - hidden=True, + ephemeral=True, ) return text = True @@ -94,36 +94,31 @@ class DevCog(commands.Cog): title = data if text else ctx.message.attachments[0].filename description = "Hashed using " + method fields = [ - Field("Data Size", data_size, False), - Field("Hash", f"`{hex}`", False), + EmbedField("Data Size", data_size, False), + EmbedField("Hash", f"`{hex}`", False), ] embed = build_embed(title=title, description=description, fields=fields) await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="uuid", - description="Generate a UUID", - options=[ - create_option( - name="version", - description="UUID version", - option_type=3, - required=True, - choices=[create_choice(name=x, value=x) for x in ["3", "4", "5"]], - ), - create_option( - name="data", - description="Data for UUID version 3,5", - option_type=3, - required=False, - ), - ], + @slash_command(name="uuid", description="Generate a UUID") + @slash_option( + name="version", + description="UUID version", + opt_type=OptionTypes.STRING, + required=True, + choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]], ) - async def _uuid(self, ctx: SlashContext, version: str, data: str = None) -> None: + @slash_option( + name="data", + description="Data for UUID version 3,5", + opt_type=OptionTypes.STRING, + required=False, + ) + async def _uuid(self, ctx: InteractionContext, version: str, data: str = None) -> None: version = int(version) if version in [3, 5] and not data: - await ctx.send(f"UUID{version} requires data.", hidden=True) + await ctx.send(f"UUID{version} requires data.", ephemeral=True) return if version == 4: await ctx.send(f"UUID4: `{uuidpy.uuid4()}`") @@ -139,40 +134,40 @@ class DevCog(commands.Cog): to_send = UUID_GET[version](uuidpy.NAMESPACE_DNS, data) await ctx.send(f"UUID{version}: `{to_send}`") - @cog_ext.cog_slash( + @slash_command( name="objectid", description="Generate an ObjectID", ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _objectid(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _objectid(self, ctx: InteractionContext) -> None: await ctx.send(f"ObjectId: `{str(ObjectId())}`") - @cog_ext.cog_slash( + @slash_command( name="ulid", description="Generate a ULID", ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _ulid(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _ulid(self, ctx: InteractionContext) -> None: await ctx.send(f"ULID: `{ulidpy.new().str}`") - @cog_ext.cog_slash( + @slash_command( name="uuid2ulid", description="Convert a UUID to a ULID", ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _uuid2ulid(self, ctx: SlashContext, uuid: str) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None: if UUID_VERIFY.match(uuid): u = ulidpy.parse(uuid) await ctx.send(f"ULID: `{u.str}`") else: await ctx.send("Invalid UUID") - @cog_ext.cog_slash( + @slash_command( name="ulid2uuid", description="Convert a ULID to a UUID", ) - @commands.cooldown(1, 2, commands.BucketType.user) - async def _ulid2uuid(self, ctx: SlashContext, ulid: str) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=2) + async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None: if ULID_VERIFY.match(ulid): ulid = ulidpy.parse(ulid) await ctx.send(f"UUID: `{ulid.uuid}`") @@ -181,82 +176,71 @@ class DevCog(commands.Cog): base64_methods = ["b64", "b16", "b32", "a85", "b85"] - @cog_ext.cog_slash( - name="encode", - description="Encode some data", - options=[ - create_option( - name="method", - description="Encode method", - option_type=3, - required=True, - choices=[create_choice(name=x, value=x) for x in base64_methods], - ), - create_option( - name="data", - description="Data to encode", - option_type=3, - required=True, - ), - ], + @slash_command(name="encode", description="Encode some data") + @slash_option( + name="method", + description="Encode method", + opt_type=OptionTypes.STRING, + required=True, + choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], ) - async def _encode(self, ctx: SlashContext, method: str, data: str) -> None: + @slash_option( + name="data", + description="Data to encode", + opt_type=OptionTypes.STRING, + required=True, + ) + async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "encode") encoded = method(data.encode("UTF-8")).decode("UTF-8") fields = [ - Field(name="Plaintext", value=f"`{data}`", inline=False), - Field(name=mstr, value=f"`{encoded}`", inline=False), + EmbedField(name="Plaintext", value=f"`{data}`", inline=False), + EmbedField(name=mstr, value=f"`{encoded}`", inline=False), ] embed = build_embed(title="Decoded Data", description="", fields=fields) await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="decode", - description="Decode some data", - options=[ - create_option( - name="method", - description="Decode method", - option_type=3, - required=True, - choices=[create_choice(name=x, value=x) for x in base64_methods], - ), - create_option( - name="data", - description="Data to encode", - option_type=3, - required=True, - ), - ], + @slash_command(name="decode", description="Decode some data") + @slash_option( + name="method", + description="Decode method", + opt_type=OptionTypes.STRING, + required=True, + choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], ) - async def _decode(self, ctx: SlashContext, method: str, data: str) -> None: + @slash_option( + name="data", + description="Data to encode", + opt_type=OptionTypes.STRING, + required=True, + ) + async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "decode") decoded = method(data.encode("UTF-8")).decode("UTF-8") if invites.search(decoded): await ctx.send( "Please don't use this to bypass invite restrictions", - hidden=True, + ephemeral=True, ) return fields = [ - Field(name="Plaintext", value=f"`{data}`", inline=False), - Field(name=mstr, value=f"`{decoded}`", inline=False), + EmbedField(name="Plaintext", value=f"`{data}`", inline=False), + EmbedField(name=mstr, value=f"`{decoded}`", inline=False), ] embed = build_embed(title="Decoded Data", description="", fields=fields) await ctx.send(embed=embed) - @cog_ext.cog_slash( - name="cloc", - description="Get J.A.R.V.I.S. lines of code", - ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _cloc(self, ctx: SlashContext) -> None: - output = subprocess.check_output(["tokei", "-C", "--sort", "code"]).decode("UTF-8") # noqa: S603, S607 + @slash_command(name="cloc", description="Get J.A.R.V.I.S. lines of code") + @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) + async def _cloc(self, ctx: InteractionContext) -> None: + output = subprocess.check_output( # noqa: S603, S607 + ["tokei", "-C", "--sort", "code"] + ).decode("UTF-8") await ctx.send(f"```\n{output}\n```") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add DevCog to J.A.R.V.I.S.""" - bot.add_cog(DevCog(bot)) + DevCog(bot) diff --git a/jarvis/cogs/error.py b/jarvis/cogs/error.py index 82f93a9..d245b0c 100644 --- a/jarvis/cogs/error.py +++ b/jarvis/cogs/error.py @@ -20,7 +20,8 @@ class ErrorHandlerCog(commands.Cog): 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", + "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}```") @@ -29,19 +30,22 @@ class ErrorHandlerCog(commands.Cog): @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.", hidden=True) + 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", - hidden=True, + "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}```", - hidden=True, + ephemeral=True, ) raise error slash.commands[ctx.command].reset_cooldown(ctx) @@ -49,4 +53,4 @@ class ErrorHandlerCog(commands.Cog): def setup(bot: commands.Bot) -> None: """Add ErrorHandlerCog to J.A.R.V.I.S.""" - bot.add_cog(ErrorHandlerCog(bot)) + ErrorHandlerCog(bot) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index a3f2a95..6c769b0 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -2,17 +2,19 @@ from datetime import datetime, timedelta import gitlab -from ButtonPaginator import Paginator -from discord import Embed -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.model import ButtonStyle -from discord_slash.utils.manage_commands import create_choice, create_option +from dis_snek import InteractionContext, 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 ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, +) from jarvis.config import get_config from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field guild_ids = [862402786116763668] @@ -20,25 +22,22 @@ guild_ids = [862402786116763668] class GitlabCog(CacheCog): """J.A.R.V.I.S. GitLab Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): super().__init__(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 self.project = self._gitlab.projects.get(29) - @cog_ext.cog_subcommand( - base="gl", - name="issue", - description="Get an issue from GitLab", - guild_ids=guild_ids, - options=[create_option(name="id", description="Issue ID", option_type=4, required=True)], + @slash_command( + name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids ) - async def _issue(self, ctx: SlashContext, id: int) -> None: + @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) + async def _issue(self, ctx: InteractionContext, id: int) -> None: try: issue = self.project.issues.get(int(id)) except gitlab.exceptions.GitlabGetError: - await ctx.send("Issue does not exist.", hidden=True) + await ctx.send("Issue does not exist.", ephemeral=True) return assignee = issue.assignee if assignee: @@ -46,7 +45,9 @@ class GitlabCog(CacheCog): else: assignee = "None" - created_at = datetime.strptime(issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") + created_at = datetime.strptime(issue.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) labels = issue.labels if labels: @@ -55,18 +56,20 @@ class GitlabCog(CacheCog): labels = "None" fields = [ - Field(name="State", value=issue.state[0].upper() + issue.state[1:]), - Field(name="Assignee", value=assignee), - Field(name="Labels", value=labels), + EmbedField(name="State", value=issue.state[0].upper() + issue.state[1:]), + EmbedField(name="Assignee", value=assignee), + EmbedField(name="Labels", value=labels), ] color = self.project.labels.get(issue.labels[0]).color - fields.append(Field(name="Created At", value=created_at)) + fields.append(EmbedField(name="Created At", value=created_at)) if issue.state == "closed": - closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") - fields.append(Field(name="Closed At", value=closed_at)) + closed_at = datetime.strptime(issue.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + fields.append(EmbedField(name="Closed At", value=closed_at)) if issue.milestone: fields.append( - Field( + EmbedField( name="Milestone", value=f"[{issue.milestone['title']}]({issue.milestone['web_url']})", inline=False, @@ -83,50 +86,49 @@ class GitlabCog(CacheCog): ) embed.set_author( name=issue.author["name"], - icon_url=issue.author["avatar_url"], + icon_url=issue.author["display_avatar"], url=issue.author["web_url"], ) - embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png") + embed.set_thumbnail( + url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png" + ) await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="gl", - name="milestone", + @slash_command( + name="gl", + sub_cmd_name="milestone", description="Get a milestone from GitLab", - guild_ids=guild_ids, - options=[ - create_option( - name="id", - description="Milestone ID", - option_type=4, - required=True, - ) - ], + scopes=guild_ids, ) - async def _milestone(self, ctx: SlashContext, id: int) -> None: + @slash_option( + name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True + ) + async def _milestone(self, ctx: InteractionContext, id: int) -> None: try: milestone = self.project.milestones.get(int(id)) except gitlab.exceptions.GitlabGetError: - await ctx.send("Milestone does not exist.", hidden=True) + await ctx.send("Milestone does not exist.", ephemeral=True) return - created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") + created_at = datetime.strptime(milestone.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) fields = [ - Field( + EmbedField( name="State", value=milestone.state[0].upper() + milestone.state[1:], ), - Field(name="Start Date", value=milestone.start_date), - Field(name="Due Date", value=milestone.due_date), - Field(name="Created At", value=created_at), + EmbedField(name="Start Date", value=milestone.start_date), + EmbedField(name="Due Date", value=milestone.due_date), + EmbedField(name="Created At", value=created_at), ] if milestone.updated_at: updated_at = datetime.strptime(milestone.updated_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( "%Y-%m-%d %H:%M:%S UTC" ) - fields.append(Field(name="Updated At", value=updated_at)) + fields.append(EmbedField(name="Updated At", value=updated_at)) if len(milestone.title) > 200: milestone.title = milestone.title[:200] + "..." @@ -143,28 +145,25 @@ class GitlabCog(CacheCog): url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) - embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png") + embed.set_thumbnail( + url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png" + ) await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="gl", - name="mergerequest", - description="Get an merge request from GitLab", - guild_ids=guild_ids, - options=[ - create_option( - name="id", - description="Merge Request ID", - option_type=4, - required=True, - ) - ], + @slash_command( + name="gl", + sub_cmd_name="mr", + description="Get a merge request from GitLab", + scopes=guild_ids, ) - async def _mergerequest(self, ctx: SlashContext, id: int) -> None: + @slash_option( + name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True + ) + async def _mergerequest(self, ctx: InteractionContext, id: int) -> None: try: mr = self.project.mergerequests.get(int(id)) except gitlab.exceptions.GitlabGetError: - await ctx.send("Merge request does not exist.", hidden=True) + await ctx.send("Merge request does not exist.", ephemeral=True) return assignee = mr.assignee if assignee: @@ -172,7 +171,9 @@ class GitlabCog(CacheCog): else: assignee = "None" - created_at = datetime.strptime(mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") + created_at = datetime.strptime(mr.created_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) labels = mr.labels if labels: @@ -181,24 +182,28 @@ class GitlabCog(CacheCog): labels = "None" fields = [ - Field(name="State", value=mr.state[0].upper() + mr.state[1:]), - Field(name="Assignee", value=assignee), - Field(name="Labels", value=labels), + EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:]), + EmbedField(name="Assignee", value=assignee), + EmbedField(name="Labels", value=labels), ] if mr.labels: color = self.project.labels.get(mr.labels[0]).color else: color = "#00FFEE" - fields.append(Field(name="Created At", value=created_at)) + fields.append(EmbedField(name="Created At", value=created_at)) if mr.state == "merged": - merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") - fields.append(Field(name="Merged At", value=merged_at)) + merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + fields.append(EmbedField(name="Merged At", value=merged_at)) elif mr.state == "closed": - closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S UTC") - fields.append(Field(name="Closed At", value=closed_at)) + closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( + "%Y-%m-%d %H:%M:%S UTC" + ) + fields.append(EmbedField(name="Closed At", value=closed_at)) if mr.milestone: fields.append( - Field( + EmbedField( name="Milestone", value=f"[{mr.milestone['title']}]({mr.milestone['web_url']})", inline=False, @@ -215,10 +220,12 @@ class GitlabCog(CacheCog): ) embed.set_author( name=mr.author["name"], - icon_url=mr.author["avatar_url"], + icon_url=mr.author["display_avatar"], url=mr.author["web_url"], ) - embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png") + embed.set_thumbnail( + url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png" + ) await ctx.send(embed=embed) def build_embed_page(self, api_list: list, t_state: str, name: str) -> Embed: @@ -230,7 +237,7 @@ class GitlabCog(CacheCog): fields = [] for item in api_list: fields.append( - Field( + EmbedField( name=f"[#{item.iid}] {item.title}", value=item.description + f"\n\n[View this {name}]({item.web_url})", inline=False, @@ -248,35 +255,32 @@ class GitlabCog(CacheCog): url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) - embed.set_thumbnail(url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png") + embed.set_thumbnail( + url="https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png" + ) return embed - @cog_ext.cog_subcommand( - base="gl", - name="issues", - description="Get open issues from GitLab", - guild_ids=guild_ids, - options=[ - create_option( - name="state", - description="State of issues to get", - option_type=3, - required=False, - choices=[ - create_choice(name="Open", value="opened"), - create_choice(name="Closed", value="closed"), - create_choice(name="All", value="all"), - ], - ) + @slash_command( + name="gl", sub_cmd_name="issues", description="Get issues from GitLab", scopes=guild_ids + ) + @slash_option( + name="state", + description="State of issues to get", + opt_type=OptionTypes.STRING, + required=False, + choices=[ + SlashCommandChoice(name="Open", value="opened"), + SlashCommandChoice(name="Closed", value="closed"), + SlashCommandChoice(name="All", value="all"), ], ) - async def _issues(self, ctx: SlashContext, state: str = "opened") -> None: + async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None: exists = self.check_cache(ctx, state=state) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return await ctx.defer() @@ -311,20 +315,9 @@ class GitlabCog(CacheCog): pages = [] t_state = t_state[0].upper() + t_state[1:] for i in range(0, len(issues), 5): - pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue")) # noqa: E203 + pages.append(self.build_embed_page(issues[i : i + 5], t_state=t_state, name="issue")) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "user": ctx.author.id, @@ -335,35 +328,32 @@ class GitlabCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) - @cog_ext.cog_subcommand( - base="gl", - name="mergerequests", - description="Get open issues from GitLab", - guild_ids=guild_ids, - options=[ - create_option( - name="state", - description="State of issues to get", - option_type=3, - required=False, - choices=[ - create_choice(name="Open", value="opened"), - create_choice(name="Closed", value="closed"), - create_choice(name="Merged", value="merged"), - create_choice(name="All", value="all"), - ], - ) + @slash_command( + name="gl", + sub_cmd_name="mrs", + description="Get merge requests from GitLab", + scopes=guild_ids, + ) + @slash_option( + name="state", + description="State of merge requests to get", + opt_type=OptionTypes.STRING, + required=False, + choices=[ + SlashCommandChoice(name="Open", value="opened"), + SlashCommandChoice(name="Closed", value="closed"), + SlashCommandChoice(name="All", value="all"), ], ) - async def _mergerequests(self, ctx: SlashContext, state: str = "opened") -> None: + async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None: exists = self.check_cache(ctx, state=state) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return await ctx.defer() @@ -398,20 +388,11 @@ class GitlabCog(CacheCog): pages = [] t_state = t_state[0].upper() + t_state[1:] for i in range(0, len(merges), 5): - pages.append(self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request")) # noqa: E203 + pages.append( + self.build_embed_page(merges[i : i + 5], t_state=t_state, name="merge request") + ) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "user": ctx.author.id, @@ -422,21 +403,21 @@ class GitlabCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) - @cog_ext.cog_subcommand( - base="gl", - name="milestones", - description="Get open issues from GitLab", - guild_ids=guild_ids, + @slash_command( + name="gl", + sub_cmd_name="milestones", + description="Get milestones from GitLab", + scopes=guild_ids, ) - async def _milestones(self, ctx: SlashContext) -> None: + async def _milestones(self, ctx: InteractionContext) -> None: exists = self.check_cache(ctx) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return await ctx.defer() @@ -463,20 +444,11 @@ class GitlabCog(CacheCog): pages = [] for i in range(0, len(milestones), 5): - pages.append(self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone")) # noqa: E203 + pages.append( + self.build_embed_page(milestones[i : i + 5], t_state=None, name="milestone") + ) - paginator = Paginator( - bot=self.bot, - ctx=ctx, - embeds=pages, - only=ctx.author, - timeout=60 * 5, # 5 minute timeout - disable_after_timeout=True, - use_extend=len(pages) > 2, - left_button_style=ButtonStyle.grey, - right_button_style=ButtonStyle.grey, - basic_buttons=["◀", "▶"], - ) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) self.cache[hash(paginator)] = { "user": ctx.author.id, @@ -486,10 +458,10 @@ class GitlabCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" if get_config().gitlab_token: - bot.add_cog(GitlabCog(bot)) + GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 0b8a342..83aff59 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -5,21 +5,23 @@ from io import BytesIO import aiohttp import cv2 import numpy as np -from discord import File -from discord.ext import commands +from dis_snek import MessageContext, Scale, Snake, message_command +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 jarvis.utils.field import Field -class ImageCog(commands.Cog): +class ImageCog(Scale): """ Image processing functions for J.A.R.V.I.S. May be categorized under util later """ - def __init__(self, bot: commands.Bot): + 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) @@ -27,7 +29,7 @@ class ImageCog(commands.Cog): def __del__(self): self._session.close() - async def _resize(self, ctx: commands.Context, target: str, url: str = None) -> None: + async def _resize(self, ctx: MessageContext, target: str, url: str = None) -> None: if not target: await ctx.send("Missing target size, i.e. 200KB.") return @@ -84,23 +86,23 @@ class ImageCog(commands.Cog): bufio = BytesIO(file) accuracy = (len(file) / tgt_size) * 100 fields = [ - Field("Original Size", convert_bytesize(size), False), - Field("New Size", convert_bytesize(len(file)), False), - Field("Accuracy", f"{accuracy:.02f}%", False), + EmbedField("Original Size", convert_bytesize(size), False), + EmbedField("New Size", convert_bytesize(len(file)), 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(bufio, filename="resized.png"), + file=File(file=bufio, filename="resized.png"), ) - @commands.command(name="resize", help="Resize an image") - @commands.cooldown(1, 60, commands.BucketType.user) - async def _resize_pref(self, ctx: commands.Context, target: str, url: str = None) -> None: + @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: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add ImageCog to J.A.R.V.I.S.""" - bot.add_cog(ImageCog(bot)) + ImageCog(bot) diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py index b298a18..0bf3fcc 100644 --- a/jarvis/cogs/jokes.py +++ b/jarvis/cogs/jokes.py @@ -5,31 +5,38 @@ import traceback from datetime import datetime from random import randint -from discord.ext import commands -from discord_slash import SlashContext, cog_ext +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.db.models import Joke from jarvis.utils import build_embed -from jarvis.utils.field import Field -class JokeCog(commands.Cog): +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: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot # TODO: Make this a command group with subcommands - @cog_ext.cog_slash( + @slash_command( name="joke", description="Hear a joke", ) - @commands.cooldown(1, 10, commands.BucketType.channel) - async def _joke(self, ctx: SlashContext, id: str = None) -> None: + @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 @@ -50,7 +57,7 @@ class JokeCog(commands.Cog): result = Joke.objects().aggregate(pipeline).next() if result is None: - await ctx.send("Humor module failed. Please try again later.", hidden=True) + await ctx.send("Humor module failed. Please try again later.", ephemeral=True) return emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"]) for match in emotes: @@ -63,7 +70,7 @@ class JokeCog(commands.Cog): body = "" for word in result["body"].split(" "): if len(body) + 1 + len(word) > 1024: - body_chunks.append(Field("​", body, False)) + body_chunks.append(EmbedField("​", body, False)) body = "" if word == "\n" and body == "": continue @@ -87,15 +94,15 @@ class JokeCog(commands.Cog): else: desc += word + " " - body_chunks.append(Field("​", body, False)) + body_chunks.append(EmbedField("​", body, False)) fields = body_chunks - fields.append(Field("Score", result["score"])) + fields.append(EmbedField("Score", result["score"])) # Field( # "Created At", # str(datetime.fromtimestamp(result["created_utc"])), # ), - fields.append(Field("ID", result["rid"])) + fields.append(EmbedField("ID", result["rid"])) embed = build_embed( title=title, description=desc, @@ -109,6 +116,6 @@ class JokeCog(commands.Cog): # await ctx.send(f"**{result['title']}**\n\n{result['body']}") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add JokeCog to J.A.R.V.I.S.""" - bot.add_cog(JokeCog(bot)) + JokeCog(bot) diff --git a/jarvis/cogs/modlog/__init__.py b/jarvis/cogs/modlog/__init__.py index 9e72344..63c09e2 100644 --- a/jarvis/cogs/modlog/__init__.py +++ b/jarvis/cogs/modlog/__init__.py @@ -6,6 +6,6 @@ from jarvis.cogs.modlog import command, member, message def setup(bot: Bot) -> None: """Add modlog cogs to J.A.R.V.I.S.""" - bot.add_cog(command.ModlogCommandCog(bot)) - bot.add_cog(member.ModlogMemberCog(bot)) - bot.add_cog(message.ModlogMessageCog(bot)) + command.ModlogCommandCog(bot) + member.ModlogMemberCog(bot) + message.ModlogMessageCog(bot) diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index e5ccd4b..75d54b5 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -41,9 +41,8 @@ class ModlogCommandCog(commands.Cog): fields=fields, color="#fc9e3f", ) - embed.set_author( - name=ctx.author.name, - icon_url=ctx.author.avatar_url, + 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}" ) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") await channel.send(embed=embed) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py index bd6cf20..99302d7 100644 --- a/jarvis/cogs/modlog/member.py +++ b/jarvis/cogs/modlog/member.py @@ -223,7 +223,9 @@ class ModlogMemberCog(commands.Cog): desc=f"{before.mention} was verified", ) - async def process_rolechange(self, before: discord.Member, after: discord.Member) -> discord.Embed: + 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( @@ -320,10 +322,7 @@ class ModlogMemberCog(commands.Cog): fields=fields, timestamp=log.created_at, ) - embed.set_author( - name=f"{after.name}", - icon_url=after.avatar_url, - ) + 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 diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py index 8c779a6..a00bb69 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -44,10 +44,12 @@ class ModlogMessageCog(commands.Cog): ) embed.set_author( name=before.author.name, - icon_url=before.author.avatar_url, + 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}") + embed.set_footer( + text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}" + ) await channel.send(embed=embed) @commands.Cog.listener() @@ -97,8 +99,10 @@ class ModlogMessageCog(commands.Cog): embed.set_author( name=message.author.name, - icon_url=message.author.avatar_url, + 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}") + embed.set_footer( + text=f"{message.author.name}#{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 8831016..28a63fb 100644 --- a/jarvis/cogs/modlog/utils.py +++ b/jarvis/cogs/modlog/utils.py @@ -33,10 +33,7 @@ def modlog_embed( fields=fields, timestamp=log.created_at, ) - embed.set_author( - name=f"{member.name}", - icon_url=member.avatar_url, - ) + embed.set_author(name=f"{member.name}", icon_url=member.display_avatar.url) embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}") return embed diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py index 7979ad0..c00118e 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -1,163 +1,27 @@ """J.A.R.V.I.S. Owner Cog.""" -import os -import sys -import traceback -from inspect import getsource -from time import time -from typing import Any +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 -import discord -from discord import DMChannel, User -from discord.ext import commands - -import jarvis from jarvis.config import reload_config from jarvis.db.models import Config -from jarvis.utils import update -from jarvis.utils.permissions import user_is_bot_admin -class OwnerCog(commands.Cog): +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: commands.Cog): + def __init__(self, bot: Snake): self.bot = bot self.admins = Config.objects(key="admins").first() - @commands.command(name="load", hidden=True) - @user_is_bot_admin() - async def _load_cog(self, ctx: commands.Context, *, cog: str) -> None: - info = await self.bot.application_info() - if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value: - try: - if "jarvis.cogs." not in cog: - cog = "jarvis.cogs." + cog.split(".")[-1] - self.bot.load_extension(cog) - except commands.errors.ExtensionAlreadyLoaded: - await ctx.send(f"Cog `{cog}` already loaded") - except Exception as e: - await ctx.send(f"Failed to load new cog `{cog}`: {type(e).name} - {e}") - else: - await ctx.send(f"Successfully loaded new cog `{cog}`") - else: - await ctx.send("I'm afraid I can't let you do that") - - @commands.command(name="unload", hidden=True) - @user_is_bot_admin() - async def _unload_cog(self, ctx: commands.Context, *, cog: str) -> None: - if cog in ["jarvis.cogs.owner", "owner"]: - await ctx.send("Cannot unload `owner` cog") - return - info = await self.bot.application_info() - if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value: - try: - if "jarvis.cogs." not in cog: - cog = "jarvis.cogs." + cog.split(".")[-1] - self.bot.unload_extension(cog) - except commands.errors.ExtensionNotLoaded: - await ctx.send(f"Cog `{cog}` not loaded") - except Exception as e: - await ctx.send(f"Failed to unload cog `{cog}` {type(e).__name__} - {e}") - else: - await ctx.send(f"Successfully unloaded cog `{cog}`") - else: - await ctx.send("I'm afraid I can't let you do that") - - @commands.command(name="reload", hidden=True) - @user_is_bot_admin() - async def _cog_reload(self, ctx: commands.Context, *, cog: str) -> None: - if cog in ["jarvis.cogs.owner", "owner"]: - await ctx.send("Cannot reload `owner` cog") - return - info = await self.bot.application_info() - if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value: - try: - if "jarvis.cogs." not in cog: - cog = "jarvis.cogs." + cog.split(".")[-1] - try: - self.bot.load_extension(cog) - except commands.errors.ExtensionNotLoaded: - pass - self.bot.unload_extension(cog) - except Exception as e: - await ctx.send(f"Failed to reload cog `{cog}` {type(e).__name__} - {e}") - else: - await ctx.send(f"Successfully reloaded cog `{cog}`") - else: - await ctx.send("I'm afraid I can't let you do that") - - @commands.group(name="system", hidden=True, pass_context=True) - @user_is_bot_admin() - async def _system(self, ctx: commands.Context) -> None: - if ctx.invoked_subcommand is None: - await ctx.send("Usage: `system `\n" + "Subcommands: `restart`, `update`") - - @_system.command(name="restart", hidden=True) - @user_is_bot_admin() - async def _restart(self, ctx: commands.Context) -> None: - info = await self.bot.application_info() - if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value: - await ctx.send("Restarting core systems...") - if isinstance(ctx.channel, discord.channel.DMChannel): - jarvis.restart_ctx = { - "user": ctx.message.author.id, - "channel": ctx.channel.id, - } - else: - jarvis.restart_ctx = { - "guild": ctx.message.guild.id, - "channel": ctx.channel.id, - } - await self.bot.close() - else: - await ctx.send("I'm afraid I can't let you do that") - - @_system.command(name="update", hidden=True) - @user_is_bot_admin() - async def _update(self, ctx: commands.Context) -> None: - info = await self.bot.application_info() - if ctx.message.author == info.owner or ctx.message.author.id in self.admins.value: - await ctx.send("Updating core systems...") - status = update() - if status == 0: - await ctx.send("Core systems updated. Restarting...") - if isinstance(ctx.channel, discord.channel.DMChannel): - jarvis.restart_ctx = { - "user": ctx.message.author.id, - "channel": ctx.channel.id, - } - else: - jarvis.restart_ctx = { - "guild": ctx.message.guild.id, - "channel": ctx.channel.id, - } - await self.bot.close() - elif status == 1: - await ctx.send("Core systems already up to date.") - elif status == 2: - await ctx.send("Core system update available, but core is dirty.") - else: - await ctx.send("I'm afraid I can't let you do that") - - @_system.command(name="refresh", hidden=True) - @user_is_bot_admin() - async def _refresh(self, ctx: commands.Context) -> None: - reload_config() - await ctx.send("System refreshed") - - @commands.group(name="admin", hidden=True, pass_context=True) - @commands.is_owner() - async def _admin(self, ctx: commands.Context) -> None: - if ctx.invoked_subcommand is None: - await ctx.send("Usage: `admin `\n" + "Subcommands: `add`, `remove`") - - @_admin.command(name="add", hidden=True) - @commands.is_owner() - async def _add(self, ctx: commands.Context, user: User) -> None: + @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 @@ -166,9 +30,9 @@ class OwnerCog(commands.Cog): reload_config() await ctx.send(f"{user.mention} is now an admin. Use this power carefully.") - @_admin.command(name="remove", hidden=True) - @commands.is_owner() - async def _remove(self, ctx: commands.Context, user: User) -> None: + @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 @@ -177,70 +41,7 @@ class OwnerCog(commands.Cog): reload_config() await ctx.send(f"{user.mention} is no longer an admin.") - def resolve_variable(self, variable: Any) -> Any: - """Resolve a variable from eval.""" - if hasattr(variable, "__iter__"): - var_length = len(list(variable)) - if (var_length > 100) and (not isinstance(variable, str)): - return f"" - elif not var_length: - return f"" - if (not variable) and (not isinstance(variable, bool)): - return f"" - return ( - variable - if (len(f"{variable}") <= 1000) - else f"" - ) - - def prepare(self, string: str) -> str: - """Prepare string for eval.""" - arr = string.strip("```").replace("py\n", "").replace("python\n", "").split("\n") - if not arr[::-1][0].replace(" ", "").startswith("return"): - arr[len(arr) - 1] = "return " + arr[::-1][0] - return "".join(f"\n\t{i}" for i in arr) - - @commands.command(pass_context=True, aliases=["eval", "exec", "evaluate"]) - @user_is_bot_admin() - async def _eval(self, ctx: commands.Context, *, code: str) -> None: - if not isinstance(ctx.message.channel, DMChannel): - return - code = self.prepare(code) - args = { - "discord": discord, - "sauce": getsource, - "sys": sys, - "os": os, - "imp": __import__, - "this": self, - "ctx": ctx, - } - - try: - exec( # noqa: S102 - f"async def func():{code}", - globals().update(args), - locals(), - ) - a = time() - response = await eval("func()", globals().update(args), locals()) # noqa: S307 - if response is None or isinstance(response, discord.Message): - del args, code - return - - if isinstance(response, str): - response = response.replace("`", "") - - await ctx.send( - f"```py\n{self.resolve_variable(response)}```\n`{type(response).__name__} | {(time() - a) / 1000} ms`" - ) - except Exception: - await ctx.send(f"Error occurred:```\n{traceback.format_exc()}```") - - del args, code - - -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add OwnerCog to J.A.R.V.I.S.""" - bot.add_cog(OwnerCog(bot)) + OwnerCog(bot) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index f17748a..70bd916 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -5,26 +5,22 @@ from datetime import datetime, timedelta from typing import List, Optional from bson import ObjectId -from discord import Embed -from discord.ext.commands import Bot -from discord.ext.tasks import loop -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option -from discord_slash.utils.manage_components import ( - create_actionrow, - create_select, - create_select_option, - wait_for_component, +from dis_snek import InteractionContext, Snake +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, + slash_command, + slash_option, ) from jarvis.db.models import Reminder from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", + r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) @@ -32,49 +28,40 @@ invites = re.compile( class RemindmeCog(CacheCog): """J.A.R.V.I.S. Remind Me Cog.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snake): super().__init__(bot) - self._remind.start() - @cog_ext.cog_slash( - name="remindme", - description="Set a reminder", - options=[ - create_option( - name="message", - description="What to remind you of", - option_type=3, - required=True, - ), - create_option( - name="weeks", - description="Number of weeks?", - option_type=4, - required=False, - ), - create_option( - name="days", - description="Number of days?", - option_type=4, - required=False, - ), - create_option( - name="hours", - description="Number of hours?", - option_type=4, - required=False, - ), - create_option( - name="minutes", - description="Number of minutes?", - option_type=4, - required=False, - ), - ], + @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="weeks", + description="Number of weeks?", + opt_type=OptionTypes.INTEGER, + 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, + required=False, ) async def _remindme( self, - ctx: SlashContext, + ctx: InteractionContext, message: Optional[str] = None, weeks: Optional[int] = 0, days: Optional[int] = 0, @@ -82,20 +69,20 @@ class RemindmeCog(CacheCog): minutes: Optional[int] = 0, ) -> None: if len(message) > 100: - await ctx.send("Reminder cannot be > 100 characters.", hidden=True) + await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True) return elif invites.search(message): await ctx.send( "Listen, don't use this to try and bypass the rules", - hidden=True, + ephemeral=True, ) return elif not valid.fullmatch(message): - await ctx.send("Hey, you should probably make this readable", hidden=True) + await ctx.send("Hey, you should probably make this readable", ephemeral=True) return if not any([weeks, days, hours, minutes]): - await ctx.send("At least one time period is required", hidden=True) + await ctx.send("At least one time period is required", ephemeral=True) return weeks = abs(weeks) @@ -104,19 +91,19 @@ class RemindmeCog(CacheCog): minutes = abs(minutes) if weeks and weeks > 4: - await ctx.send("Cannot be farther than 4 weeks out!", hidden=True) + await ctx.send("Cannot be farther than 4 weeks out!", ephemeral=True) return elif days and days > 6: - await ctx.send("Use weeks instead of 7+ days, please.", hidden=True) + await ctx.send("Use weeks instead of 7+ days, please.", ephemeral=True) return elif hours and hours > 23: - await ctx.send("Use days instead of 24+ hours, please.", hidden=True) + await ctx.send("Use days instead of 24+ hours, please.", ephemeral=True) return elif minutes and minutes > 59: - await ctx.send("Use hours instead of 59+ minutes, please.", hidden=True) + await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True) return reminders = Reminder.objects(user=ctx.author.id, active=True).count() @@ -124,7 +111,7 @@ class RemindmeCog(CacheCog): await ctx.send( "You already have 5 (or more) active reminders. " "Please either remove an old one, or wait for one to pass", - hidden=True, + ephemeral=True, ) return @@ -148,8 +135,8 @@ class RemindmeCog(CacheCog): title="Reminder Set", description=f"{ctx.author.mention} set a reminder", fields=[ - Field(name="Message", value=message), - Field( + EmbedField(name="Message", value=message), + EmbedField( name="When", value=remind_at.strftime("%Y-%m-%d %H:%M UTC"), inline=False, @@ -158,19 +145,21 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar.url) await ctx.send(embed=embed) - async def get_reminders_embed(self, ctx: SlashContext, reminders: List[Reminder]) -> Embed: + async def get_reminders_embed( + self, ctx: InteractionContext, reminders: List[Reminder] + ) -> Embed: """Build embed for paginator.""" fields = [] for reminder in reminders: fields.append( - Field( + EmbedField( name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), value=f"{reminder.message}\n\u200b", inline=False, @@ -184,57 +173,49 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar.url) return embed - @cog_ext.cog_subcommand( - base="reminders", - name="list", - description="List reminders for a user", - ) - async def _list(self, ctx: SlashContext) -> None: + @slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders") + async def _list(self, ctx: InteractionContext) -> None: exists = self.check_cache(ctx) if exists: - await ctx.defer(hidden=True) + await ctx.defer(ephemeral=True) await ctx.send( f"Please use existing interaction: {exists['paginator']._message.jump_url}", - hidden=True, + ephemeral=True, ) return reminders = Reminder.objects(user=ctx.author.id, active=True) if not reminders: - await ctx.send("You have no reminders set.", hidden=True) + await ctx.send("You have no reminders set.", ephemeral=True) return embed = await self.get_reminders_embed(ctx, reminders) await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="reminders", - name="delete", - description="Delete a reminder", - ) - async def _delete(self, ctx: SlashContext) -> None: + @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) if not reminders: - await ctx.send("You have no reminders set", hidden=True) + await ctx.send("You have no reminders set", ephemeral=True) return options = [] for reminder in reminders: - option = create_select_option( + option = SelectOption( label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), value=str(reminder.id), emoji="⏰", ) options.append(option) - select = create_select( + select = Select( options=options, custom_id="to_delete", placeholder="Select reminders to delete", @@ -242,7 +223,7 @@ class RemindmeCog(CacheCog): max_values=len(reminders), ) - components = [create_actionrow(select)] + components = [ActionRow(select)] embed = await self.get_reminders_embed(ctx, reminders) message = await ctx.send( content=f"You have {len(reminders)} reminder(s) set:", @@ -251,23 +232,22 @@ class RemindmeCog(CacheCog): ) try: - context = await wait_for_component( - self.bot, - check=lambda x: ctx.author.id == x.author_id, + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) - for to_delete in context.selected_options: + 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 + for component in row.components: + component.disabled = True fields = [] - for reminder in filter(lambda x: str(x.id) in context.selected_options, reminders): + for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): fields.append( - Field( + EmbedField( name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), value=reminder.message, inline=False, @@ -280,52 +260,23 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + name=ctx.author.username + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar.url) - await context.edit_origin( - content=f"Deleted {len(context.selected_options)} reminder(s)", + await context.context.edit_origin( + content=f"Deleted {len(context.context.values)} reminder(s)", components=components, embed=embed, ) except asyncio.TimeoutError: for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True await message.edit(components=components) - @loop(seconds=15) - async def _remind(self) -> None: - reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30)) - for reminder in reminders: - if reminder.remind_at <= datetime.utcnow(): - user = await self.bot.fetch_user(reminder.user) - if not user: - reminder.delete() - continue - embed = build_embed( - title="You have a reminder", - description=reminder.message, - fields=[], - ) - embed.set_author( - name=user.name + "#" + user.discriminator, - icon_url=user.avatar_url, - ) - embed.set_thumbnail(url=user.avatar_url) - try: - await user.send(embed=embed) - except Exception: - guild = self.bot.fetch_guild(reminder.guild) - channel = guild.get_channel(reminder.channel) if guild else None - if channel: - await channel.send(f"{user.mention}", embed=embed) - finally: - reminder.delete() - -def setup(bot: Bot) -> None: +def setup(bot: Snake) -> None: """Add RemindmeCog to J.A.R.V.I.S.""" - bot.add_cog(RemindmeCog(bot)) + RemindmeCog(bot) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index ac27d14..8b87241 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,54 +1,45 @@ """J.A.R.V.I.S. Role Giver Cog.""" import asyncio -from discord import Role -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option -from discord_slash.utils.manage_components import ( - create_actionrow, - create_select, - create_select_option, - wait_for_component, +from dis_snek import InteractionContext, Permissions, Scale, Snake +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 +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, ) +from dis_snek.models.snek.command import check, cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.db.models import Rolegiver -from jarvis.utils import build_embed -from jarvis.utils.field import Field +from jarvis.utils import build_embed, get from jarvis.utils.permissions import admin_or_permissions -class RolegiverCog(commands.Cog): +class RolegiverCog(Scale): """J.A.R.V.I.S. Role Giver Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @cog_ext.cog_subcommand( - base="rolegiver", - name="add", - description="Add a role to rolegiver", - options=[ - create_option( - name="role", - description="Role to add", - option_type=8, - required=True, - ) - ], + @slash_command( + name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver" ) - @admin_or_permissions(manage_guild=True) - async def _rolegiver_add(self, ctx: SlashContext, role: Role) -> None: + @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() if setting and role.id in setting.roles: - await ctx.send("Role already in rolegiver", hidden=True) + await ctx.send("Role already in rolegiver", ephemeral=True) return if not setting: setting = Rolegiver(guild=ctx.guild.id, roles=[]) if len(setting.roles) >= 20: - await ctx.send("You can only have 20 roles in the rolegiver", hidden=True) + await ctx.send("You can only have 20 roles in the rolegiver", ephemeral=True) return setting.roles.append(role.id) @@ -58,7 +49,7 @@ class RolegiverCog(commands.Cog): for role_id in setting.roles: if role_id == role.id: continue - e_role = ctx.guild.get_role(role_id) + e_role = await ctx.guild.get_role(role_id) if not e_role: continue roles.append(e_role) @@ -67,8 +58,8 @@ class RolegiverCog(commands.Cog): value = "\n".join([r.mention for r in roles]) if roles else "None" fields = [ - Field(name="New Role", value=f"{role.mention}"), - Field(name="Existing Role(s)", value=value), + EmbedField(name="New Role", value=f"{role.mention}"), + EmbedField(name="Existing Role(s)", value=value), ] embed = build_embed( @@ -77,61 +68,60 @@ class RolegiverCog(commands.Cog): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) - embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.name, - icon_url=ctx.author.avatar_url, - ) + embed.set_thumbnail(url=ctx.guild.icon.url) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") + embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}") await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="rolegiver", - name="remove", - description="Remove a role from rolegiver", + @slash_command( + name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver" ) - @admin_or_permissions(manage_guild=True) - async def _rolegiver_remove(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _rolegiver_remove(self, ctx: InteractionContext) -> None: setting = Rolegiver.objects(guild=ctx.guild.id).first() if not setting or (setting and not setting.roles): - await ctx.send("Rolegiver has no roles", hidden=True) + await ctx.send("Rolegiver has no roles", ephemeral=True) return options = [] for role in setting.roles: - role: Role = ctx.guild.get_role(role) - option = create_select_option(label=role.name, value=str(role.id)) + role: Role = await ctx.guild.get_role(role) + option = SelectOption(label=role.name, value=str(role.id)) options.append(option) - select = create_select( + select = Select( options=options, custom_id="to_delete", placeholder="Select roles to remove", min_values=1, max_values=len(options), ) - components = [create_actionrow(select)] + components = [ActionRow(select)] message = await ctx.send(content="\u200b", components=components) try: - context = await wait_for_component( - self.bot, - check=lambda x: ctx.author.id == x.author.id, - message=message, + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, timeout=60 * 1, ) - for to_delete in context.selected_options: + removed_roles = [] + for to_delete in context.context.values: + role = await ctx.guild.get_role(to_delete) + if role: + removed_roles.append(role) setting.roles.remove(int(to_delete)) setting.save() + for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True roles = [] for role_id in setting.roles: - e_role = ctx.guild.get_role(role_id) + e_role = await ctx.guild.get_role(role_id) if not e_role: continue roles.append(e_role) @@ -140,9 +130,10 @@ class RolegiverCog(commands.Cog): roles.sort(key=lambda x: -x.position) value = "\n".join([r.mention for r in roles]) if roles else "None" + rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None" fields = [ - Field(name="Removed Role", value=f"{role.mention}"), - Field(name="Remaining Role(s)", value=value), + EmbedField(name="Removed Role(s)", value=rvalue), + EmbedField(name="Remaining Role(s)", value=value), ] embed = build_embed( @@ -151,39 +142,34 @@ class RolegiverCog(commands.Cog): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) - embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.name, - icon_url=ctx.author.avatar_url, + embed.set_thumbnail(url=ctx.guild.icon.url) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) + + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" ) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") - - await context.edit_origin( - content=f"Removed {len(context.selected_options)} role(s)", + await context.context.edit_origin( + content=f"Removed {len(context.context.values)} role(s)", embed=embed, components=components, ) except asyncio.TimeoutError: for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True await message.edit(components=components) - @cog_ext.cog_subcommand( - base="rolegiver", - name="list", - description="List roles rolegiver", - ) - async def _rolegiver_list(self, ctx: SlashContext) -> None: + @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() if not setting or (setting and not setting.roles): - await ctx.send("Rolegiver has no roles", hidden=True) + await ctx.send("Rolegiver has no roles", ephemeral=True) return roles = [] for role_id in setting.roles: - e_role = ctx.guild.get_role(role_id) + e_role = await ctx.guild.get_role(role_id) if not e_role: continue roles.append(e_role) @@ -199,59 +185,52 @@ class RolegiverCog(commands.Cog): fields=[], ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.name, - icon_url=ctx.author.avatar_url, + name=ctx.author.display_name, + icon_url=ctx.author.display_avatar.url, ) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") + embed.set_footer(text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}") await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="role", - name="get", - description="Get a role from rolegiver", - ) - @commands.cooldown(1, 10, commands.BucketType.user) - async def _role_get(self, ctx: SlashContext) -> None: + @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() if not setting or (setting and not setting.roles): - await ctx.send("Rolegiver has no roles", hidden=True) + await ctx.send("Rolegiver has no roles", ephemeral=True) return options = [] for role in setting.roles: - role: Role = ctx.guild.get_role(role) - option = create_select_option(label=role.name, value=str(role.id)) + role: Role = await ctx.guild.get_role(role) + option = SelectOption(label=role.name, value=str(role.id)) options.append(option) - select = create_select( + select = Select( options=options, - custom_id="to_delete", placeholder="Select roles to add", min_values=1, max_values=len(options), ) - components = [create_actionrow(select)] + components = [ActionRow(select)] message = await ctx.send(content="\u200b", components=components) try: - - context = await wait_for_component( - self.bot, - check=lambda x: ctx.author.id == x.author.id, + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) added_roles = [] - for role in context.selected_options: - role = ctx.guild.get_role(int(role)) + for role in context.context.values: + role = await ctx.guild.get_role(int(role)) added_roles.append(role) - await ctx.author.add_roles(role, reason="Rolegiver") + await ctx.author.add_role(role, reason="Rolegiver") roles = ctx.author.roles if roles: @@ -261,101 +240,125 @@ class RolegiverCog(commands.Cog): avalue = "\n".join([r.mention for r in added_roles]) if added_roles else "None" value = "\n".join([r.mention for r in roles]) if roles else "None" fields = [ - Field(name="Added Role(s)", value=avalue), - Field(name="Prior Role(s)", value=value), + EmbedField(name="Added Role(s)", value=avalue), + EmbedField(name="Prior Role(s)", value=value), ] embed = build_embed( title="User Given Role", - description=f"{role.mention} given to {ctx.author.mention}", + description=f"{len(added_roles)} role(s) given to {ctx.author.mention}", fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.name, - icon_url=ctx.author.avatar_url, + name=ctx.author.display_name, + icon_url=ctx.author.display_avatar.url, ) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") - for row in components: - for component in row["components"]: - component["disabled"] = True + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) - await message.edit_origin(embed=embed, content="\u200b", components=components) + for row in components: + for component in row.components: + component.disabled = True + + await context.context.edit_origin(embed=embed, content="\u200b", components=components) except asyncio.TimeoutError: for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True await message.edit(components=components) - @cog_ext.cog_subcommand( - base="role", - name="forfeit", - description="Have rolegiver take away role", - options=[ - create_option( - name="role", - description="Role to remove", - option_type=8, - required=True, - ) - ], - ) - @commands.cooldown(1, 10, commands.BucketType.user) - async def _role_forfeit(self, ctx: SlashContext, role: Role) -> None: + @slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @cooldown(bucket=Buckets.USER, rate=1, interval=10) + async def _role_remove(self, ctx: InteractionContext) -> None: + user_roles = ctx.author.roles + setting = Rolegiver.objects(guild=ctx.guild.id).first() if not setting or (setting and not setting.roles): - await ctx.send("Rolegiver has no roles", hidden=True) + await ctx.send("Rolegiver has no roles", ephemeral=True) return - elif role.id not in setting.roles: - await ctx.send("Role not in rolegiver", hidden=True) - return - elif role not in ctx.author.roles: - await ctx.send("You do not have that role", hidden=True) + elif not any(x.id in setting.roles for x in user_roles): + await ctx.send("You have no rolegiver roles", ephemeral=True) return - await ctx.author.remove_roles(role, reason="Rolegiver") + valid = list(filter(lambda x: x.id in setting.roles, user_roles)) + options = [] + for role in valid: + option = SelectOption(label=role.name, value=str(role.id)) + options.append(option) - roles = ctx.author.roles - if roles: - roles.sort(key=lambda x: -x.position) - _ = roles.pop(-1) - - value = "\n".join([r.mention for r in roles]) if roles else "None" - fields = [ - Field(name="Taken Role", value=f"{role.mention}"), - Field(name="Remaining Role(s)", value=value), - ] - - embed = build_embed( - title="User Forfeited Role", - description=f"{role.mention} taken from {ctx.author.mention}", - fields=fields, + select = Select( + options=options, + custom_id="to_remove", + placeholder="Select roles to remove", + min_values=1, + max_values=len(options), ) + components = [ActionRow(select)] - embed.set_thumbnail(url=ctx.guild.icon_url) - embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.name, - icon_url=ctx.author.avatar_url, - ) + message = await ctx.send(content="\u200b", components=components) - embed.set_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") + try: + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, + timeout=60 * 5, + ) - await ctx.send(embed=embed) + removed_roles = [] + for to_remove in context.context.values: + role = get(user_roles, id=int(to_remove)) + await ctx.author.remove_role(role, reason="Rolegiver") + user_roles.remove(role) + removed_roles.append(role) - @cog_ext.cog_subcommand( - base="rolegiver", - name="cleanup", - description="Cleanup rolegiver roles", + user_roles.sort(key=lambda x: -x.position) + _ = user_roles.pop(-1) + + value = "\n".join([r.mention for r in user_roles]) if user_roles else "None" + rvalue = "\n".join([r.mention for r in removed_roles]) if removed_roles else "None" + fields = [ + EmbedField(name="Removed Role(s)", value=rvalue), + EmbedField(name="Remaining Role(s)", value=value), + ] + + embed = build_embed( + title="User Forfeited Role", + description=f"{len(removed_roles)} role(s) removed from {ctx.author.mention}", + fields=fields, + ) + + embed.set_thumbnail(url=ctx.guild.icon.url) + embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.display_avatar.url) + + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) + + for row in components: + for component in row.components: + component.disabled = True + + await context.context.edit_origin(embed=embed, components=components, content="\u200b") + + except asyncio.TimeoutError: + for row in components: + for component in row.components: + component.disabled = True + await message.edit(components=components) + + @slash_command( + name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" ) - @admin_or_permissions(manage_guild=True) - async def _rolegiver_cleanup(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: setting = Rolegiver.objects(guild=ctx.guild.id).first() if not setting or not setting.roles: - await ctx.send("Rolegiver has no roles", hidden=True) - guild_roles = await ctx.guild.fetch_roles() - guild_role_ids = [x.id for x in guild_roles] + await ctx.send("Rolegiver has no roles", ephemeral=True) + guild_role_ids = [r.id for r in ctx.guild.roles] for role_id in setting.roles: if role_id not in guild_role_ids: setting.roles.remove(role_id) @@ -364,6 +367,6 @@ class RolegiverCog(commands.Cog): await ctx.send("Rolegiver cleanup finished") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add RolegiverCog to J.A.R.V.I.S.""" - bot.add_cog(RolegiverCog(bot)) + RolegiverCog(bot) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 622a1d7..52da039 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,6 +1,7 @@ """J.A.R.V.I.S. Settings Management Cog.""" from typing import Any +from dis_snek.models.snek.command import check from discord import Role, TextChannel from discord.ext import commands from discord.utils import find @@ -33,46 +34,24 @@ class SettingsCog(commands.Cog): """Delete a guild setting.""" return Setting.objects(setting=setting, guild=guild).delete() - @cog_ext.cog_subcommand( - base="settings", - base_desc="Settings management", - subcommand_group="set", - subcommand_group_description="Set a setting", - name="mute", - description="Set mute role", - options=[ - create_option( - name="role", - description="Mute role", - option_type=8, - required=True, - ) - ], - ) - @admin_or_permissions(manage_guild=True) - async def _set_mute(self, ctx: SlashContext, role: Role) -> None: - await ctx.defer() - self.update_settings("mute", role.id, ctx.guild.id) - await ctx.send(f"Settings applied. New mute role is `{role.name}`") - @cog_ext.cog_subcommand( base="settings", subcommand_group="set", name="modlog", description="Set modlog channel", - options=[ + choices=[ create_option( name="channel", description="Modlog channel", - option_type=7, + opt_type=7, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None: if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", hidden=True) + await ctx.send("Channel must be a TextChannel", ephemeral=True) return self.update_settings("modlog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New modlog channel is {channel.mention}") @@ -82,19 +61,19 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="userlog", description="Set userlog channel", - options=[ + choices=[ create_option( name="channel", description="Userlog channel", - option_type=7, + opt_type=7, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None: if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", hidden=True) + 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}") @@ -104,16 +83,16 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="massmention", description="Set massmention amount", - options=[ + choices=[ create_option( name="amount", description="Amount of mentions (0 to disable)", - option_type=4, + opt_type=4, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_massmention(self, ctx: SlashContext, amount: int) -> None: await ctx.defer() self.update_settings("massmention", amount, ctx.guild.id) @@ -124,16 +103,16 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="verified", description="Set verified role", - options=[ + choices=[ create_option( name="role", description="verified role", - option_type=8, + opt_type=8, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_verified(self, ctx: SlashContext, role: Role) -> None: await ctx.defer() self.update_settings("verified", role.id, ctx.guild.id) @@ -144,16 +123,16 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="unverified", description="Set unverified role", - options=[ + choices=[ create_option( name="role", description="Unverified role", - option_type=8, + opt_type=8, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_unverified(self, ctx: SlashContext, role: Role) -> None: await ctx.defer() self.update_settings("unverified", role.id, ctx.guild.id) @@ -164,41 +143,28 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="noinvite", description="Set if invite deletion should happen", - options=[ + choices=[ create_option( name="active", description="Active?", - option_type=4, + opt_type=4, required=True, ) ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _set_invitedel(self, ctx: SlashContext, active: int) -> None: await ctx.defer() self.update_settings("noinvite", bool(active), ctx.guild.id) await ctx.send(f"Settings applied. Automatic invite active: {bool(active)}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - subcommand_group_description="Unset a setting", - name="mute", - description="Unset mute role", - ) - @admin_or_permissions(manage_guild=True) - async def _unset_mute(self, ctx: SlashContext) -> None: - await ctx.defer() - self.delete_settings("mute", ctx.guild.id) - await ctx.send("Setting removed.") - @cog_ext.cog_subcommand( base="settings", subcommand_group="unset", name="modlog", description="Unset modlog channel", ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _unset_modlog(self, ctx: SlashContext) -> None: self.delete_settings("modlog", ctx.guild.id) await ctx.send("Setting removed.") @@ -209,7 +175,7 @@ class SettingsCog(commands.Cog): name="userlog", description="Unset userlog channel", ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _unset_userlog(self, ctx: SlashContext) -> None: self.delete_settings("userlog", ctx.guild.id) await ctx.send("Setting removed.") @@ -220,7 +186,7 @@ class SettingsCog(commands.Cog): name="massmention", description="Unet massmention amount", ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _massmention(self, ctx: SlashContext) -> None: await ctx.defer() self.delete_settings("massmention", ctx.guild.id) @@ -232,7 +198,7 @@ class SettingsCog(commands.Cog): name="verified", description="Unset verified role", ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _verified(self, ctx: SlashContext) -> None: await ctx.defer() self.delete_settings("verified", ctx.guild.id) @@ -244,14 +210,14 @@ class SettingsCog(commands.Cog): name="unverified", description="Unset unverified role", ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _unverified(self, ctx: SlashContext) -> None: await ctx.defer() self.delete_settings("unverified", ctx.guild.id) await ctx.send("Setting removed.") @cog_ext.cog_subcommand(base="settings", name="view", description="View settings") - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _view(self, ctx: SlashContext) -> None: settings = Setting.objects(guild=ctx.guild.id) @@ -272,7 +238,7 @@ class SettingsCog(commands.Cog): value = "||`[redacted]`||" elif setting.setting == "rolegiver": value = "" - for role in setting.value: + for _role in setting.value: nvalue = find(lambda x: x.id == value, ctx.guild.roles) if value: value += "\n" + nvalue.mention @@ -285,7 +251,7 @@ class SettingsCog(commands.Cog): await ctx.send(embed=embed) @cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings") - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _clear(self, ctx: SlashContext) -> None: deleted = Setting.objects(guild=ctx.guild.id).delete() await ctx.send(f"Guild settings cleared: `{deleted is not None}`") @@ -293,4 +259,4 @@ class SettingsCog(commands.Cog): def setup(bot: commands.Bot) -> None: """Add SettingsCog to J.A.R.V.I.S.""" - bot.add_cog(SettingsCog(bot)) + SettingsCog(bot) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index df5d1d6..79442e6 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,20 +1,19 @@ """J.A.R.V.I.S. Starboard Cog.""" -from discord import TextChannel -from discord.ext import commands -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.context import MenuContext -from discord_slash.model import ContextMenuType, SlashMessage -from discord_slash.utils.manage_commands import create_option -from discord_slash.utils.manage_components import ( - create_actionrow, - create_select, - create_select_option, - wait_for_component, +from dis_snek import InteractionContext, Permissions, Scale, Snake +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 +from dis_snek.models.snek.application_commands import ( + CommandTypes, + OptionTypes, + context_menu, + slash_command, + slash_option, ) +from dis_snek.models.snek.command import check from jarvis.db.models import Star, Starboard -from jarvis.utils import build_embed +from jarvis.utils import build_embed, find from jarvis.utils.permissions import admin_or_permissions supported_images = [ @@ -26,19 +25,15 @@ supported_images = [ ] -class StarboardCog(commands.Cog): +class StarboardCog(Scale): """J.A.R.V.I.S. Starboard Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @cog_ext.cog_subcommand( - base="starboard", - name="list", - description="Lists all Starboards", - ) - @admin_or_permissions(manage_guild=True) - async def _list(self, ctx: SlashContext) -> None: + @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) if starboards != []: message = "Available Starboards:\n" @@ -48,39 +43,35 @@ class StarboardCog(commands.Cog): else: await ctx.send("No Starboards available.") - @cog_ext.cog_subcommand( - base="starboard", - name="create", - description="Create a starboard", - options=[ - create_option( - name="channel", - description="Starboard channel", - option_type=7, - required=True, - ), - ], + @slash_command( + name="starboard", sub_cmd_name="create", sub_cmd_description="Create a starboard" ) - @admin_or_permissions(manage_guild=True) - async def _create(self, ctx: SlashContext, channel: TextChannel) -> None: + @slash_option( + name="channel", + description="Starboard channel", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _create(self, ctx: InteractionContext, channel: GuildText) -> None: if channel not in ctx.guild.channels: await ctx.send( "Channel not in guild. Choose an existing channel.", - hidden=True, + ephemeral=True, ) return - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", hidden=True) + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText", ephemeral=True) return exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first() if exists: - await ctx.send(f"Starboard already exists at {channel.mention}.", hidden=True) + await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True) return count = Starboard.objects(guild=ctx.guild.id).count() if count >= 25: - await ctx.send("25 starboard limit reached", hidden=True) + await ctx.send("25 starboard limit reached", ephemeral=True) return _ = Starboard( @@ -90,95 +81,89 @@ class StarboardCog(commands.Cog): ).save() await ctx.send(f"Starboard created. Check it out at {channel.mention}.") - @cog_ext.cog_subcommand( - base="starboard", - name="delete", - description="Delete a starboard", - options=[ - create_option( - name="channel", - description="Starboard channel", - option_type=7, - required=True, - ), - ], + @slash_command( + name="starboard", sub_cmd_name="delete", sub_cmd_description="Delete a starboard" ) - @admin_or_permissions(manage_guild=True) - async def _delete(self, ctx: SlashContext, channel: TextChannel) -> None: + @slash_option( + name="channel", + description="Starboard channel", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @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() - await ctx.send(f"Starboard deleted from {channel.mention}.", hidden=True) + await ctx.send(f"Starboard deleted from {channel.mention}.") else: - await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True) + await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True) - @cog_ext.cog_context_menu(name="Star Message", target=ContextMenuType.MESSAGE) - async def _star_message(self, ctx: MenuContext) -> None: - await self._star_add.invoke(ctx, ctx.target_message) + @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)) - @cog_ext.cog_subcommand( - base="star", - name="add", - description="Star a message", - options=[ - create_option( - name="message", - description="Message to star", - option_type=3, - required=True, - ), - create_option( - name="channel", - description="Channel that has the message, required if different than command message", - option_type=7, - required=False, - ), - ], + @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 ) - @admin_or_permissions(manage_guild=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: SlashContext, + ctx: InteractionContext, message: str, - channel: TextChannel = None, + channel: GuildText = None, ) -> None: if not channel: channel = ctx.channel starboards = Starboard.objects(guild=ctx.guild.id) if not starboards: - await ctx.send("No starboards exist.", hidden=True) + await ctx.send("No starboards exist.", ephemeral=True) return await ctx.defer() + + if not isinstance(message, Message): + if message.startswith("https://"): + message = message.split("/")[-1] + message = await channel.get_message(int(message)) + + if not message: + await ctx.send("Message not found", ephemeral=True) + return + channel_list = [] for starboard in starboards: channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels)) - select_channels = [create_select_option(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list)] + select_channels = [ + SelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list) + ] - select = create_select( + select = Select( options=select_channels, min_values=1, max_values=1, ) - components = [create_actionrow(select)] + components = [ActionRow(select)] msg = await ctx.send(content="Choose a starboard", components=components) - com_ctx = await wait_for_component( - self.bot, + com_ctx = await self.bot.wait_for_component( messages=msg, components=components, - check=lambda x: x.author.id == ctx.author.id, + check=lambda x: ctx.author.id == x.context.author.id, ) - starboard = channel_list[int(com_ctx.selected_options[0])] - - if not isinstance(message, SlashMessage): - if message.startswith("https://"): - message = message.split("/")[-1] - message = await channel.fetch_message(message) + starboard = channel_list[int(com_ctx.context.values[0])] exists = Star.objects( message=message.id, @@ -190,7 +175,7 @@ class StarboardCog(commands.Cog): if exists: await ctx.send( f"Message already sent to Starboard {starboard.mention}", - hidden=True, + ephemeral=True, ) return @@ -215,9 +200,9 @@ class StarboardCog(commands.Cog): timestamp=message.created_at, ) embed.set_author( - name=message.author.name, + name=message.author.display_name, url=message.jump_url, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar.url, ) embed.set_footer(text=message.guild.name + " | " + message.channel.name) if image_url: @@ -236,47 +221,38 @@ class StarboardCog(commands.Cog): active=True, ).save() - components[0]["components"][0]["disabled"] = True + components[0].components[0].disabled = True - await com_ctx.edit_origin( + await com_ctx.context.edit_origin( content=f"Message saved to Starboard.\nSee it in {starboard.mention}", components=components, ) - @cog_ext.cog_subcommand( - base="star", - name="delete", - description="Delete a starred message", - options=[ - create_option( - name="id", - description="Star to delete", - option_type=4, - required=True, - ), - create_option( - name="starboard", - description="Starboard to delete star from", - option_type=7, - required=True, - ), - ], + @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 ) - @admin_or_permissions(manage_guild=True) + @slash_option( + name="starboard", + description="Starboard to delete star from", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _star_delete( self, - ctx: SlashContext, + ctx: InteractionContext, id: int, - starboard: TextChannel, + starboard: GuildText, ) -> None: - if not isinstance(starboard, TextChannel): - await ctx.send("Channel must be a TextChannel", hidden=True) + 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() if not exists: await ctx.send( f"Starboard does not exist in {starboard.mention}. Please create it first", - hidden=True, + ephemeral=True, ) return @@ -287,19 +263,19 @@ class StarboardCog(commands.Cog): active=True, ).first() if not star: - await ctx.send(f"No star exists with id {id}", hidden=True) + await ctx.send(f"No star exists with id {id}", ephemeral=True) return - message = await starboard.fetch_message(star.star) + message = await starboard.get_message(star.star) if message: await message.delete() star.active = False star.save() - await ctx.send(f"Star {id} deleted") + await ctx.send(f"Star {id} deleted from {starboard.mention}") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add StarboardCog to J.A.R.V.I.S.""" - bot.add_cog(StarboardCog(bot)) + StarboardCog(bot) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index c007585..bd92ddc 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,131 +1,93 @@ """J.A.R.V.I.S. Twitter Cog.""" import asyncio -import logging import tweepy from bson import ObjectId -from discord import TextChannel -from discord.ext import commands -from discord.ext.tasks import loop -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.model import SlashCommandOptionType as COptionType -from discord_slash.utils.manage_commands import create_choice, create_option -from discord_slash.utils.manage_components import ( - create_actionrow, - create_select, - create_select_option, - wait_for_component, +from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, ) +from dis_snek.models.snek.command import check from jarvis.config import get_config from jarvis.db.models import Twitter from jarvis.utils.permissions import admin_or_permissions -logger = logging.getLogger("discord") - -class TwitterCog(commands.Cog): +class TwitterCog(Scale): """J.A.R.V.I.S. Twitter Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot config = get_config() - auth = tweepy.AppAuthHandler(config.twitter["consumer_key"], config.twitter["consumer_secret"]) + auth = tweepy.AppAuthHandler( + config.twitter["consumer_key"], config.twitter["consumer_secret"] + ) self.api = tweepy.API(auth) - self._tweets.start() self._guild_cache = {} self._channel_cache = {} - @loop(seconds=30) - async def _tweets(self) -> None: - twitters = Twitter.objects(active=True) - handles = Twitter.objects.distinct("handle") - twitter_data = {} - for handle in handles: - try: - twitter_data[handle] = self.api.user_timeline(screen_name=handle) - except Exception as e: - logger.error(f"Error with fetching: {e}") - for twitter in twitters: - try: - tweets = list(filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle])) - if tweets: - tweets = sorted(tweets, key=lambda x: x.id) - if twitter.guild not in self._guild_cache: - self._guild_cache[twitter.guild] = await self.bot.fetch_guild(twitter.guild) - guild = self._guild_cache[twitter.guild] - if twitter.channel not in self._channel_cache: - channels = await guild.fetch_channels() - self._channel_cache[twitter.channel] = find(lambda x: x.id == twitter.channel, channels) - channel = self._channel_cache[twitter.channel] - 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.last_tweet = newest.id - twitter.save() - except Exception as e: - logger.error(f"Error with tweets: {e}") - - @cog_ext.cog_subcommand( - base="twitter", - base_description="Twitter commands", - name="follow", - description="Follow a Twitter account", - options=[ - create_option(name="handle", description="Twitter account", option_type=COptionType.STRING, required=True), - create_option( - name="channel", - description="Channel to post tweets into", - option_type=COptionType.CHANNEL, - required=True, - ), - create_option( - name="retweets", - description="Mirror re-tweets?", - option_type=COptionType.STRING, - required=False, - choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")], - ), + @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") + @slash_option( + name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="channel", + description="Channel to post tweets to", + opt_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="retweets", + description="Mirror re-tweets?", + opt_type=OptionTypes.STRING, + required=False, + choices=[ + SlashCommandChoice(name="Yes", value="Yes"), + SlashCommandChoice(name="No", value="No"), ], ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_follow( - self, ctx: SlashContext, handle: str, channel: TextChannel, retweets: str = "Yes" + self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes" ) -> None: + handle = handle.lower() retweets = retweets == "Yes" if len(handle) > 15: - await ctx.send("Invalid Twitter handle", hidden=True) + await ctx.send("Invalid Twitter handle", ephemeral=True) return - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a text channel", hidden=True) + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a text channel", ephemeral=True) return try: - latest_tweet = self.api.user_timeline(screen_name=handle, count=1)[0] + account = (await asyncio.to_thread(self.api.get_user(screen_name=handle)))[0] + latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0] except Exception: - await ctx.send("Unable to get user timeline. Are you sure the handle is correct?", hidden=True) + await ctx.send( + "Unable to get user timeline. Are you sure the handle is correct?", ephemeral=True + ) return count = Twitter.objects(guild=ctx.guild.id).count() if count >= 12: - await ctx.send("Cannot follow more than 12 Twitter accounts", hidden=True) + await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) return - exists = Twitter.objects(handle=handle, guild=ctx.guild.id) + exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id) if exists: - await ctx.send("Twitter handle already being followed in this guild", hidden=True) + await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return t = Twitter( - handle=handle, + handle=account.screen_name, + twitter_id=account.id, guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, @@ -137,27 +99,25 @@ class TwitterCog(commands.Cog): await ctx.send(f"Now following `@{handle}` in {channel.mention}") - @cog_ext.cog_subcommand( - base="twitter", - name="unfollow", - description="Unfollow Twitter accounts", - ) - @admin_or_permissions(manage_guild=True) - async def _twitter_unfollow(self, ctx: SlashContext) -> None: + @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) if not twitters: - await ctx.send("You need to follow a Twitter account first", hidden=True) + await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] handlemap = {str(x.id): x.handle for x in twitters} for twitter in twitters: - option = create_select_option(label=twitter.handle, value=str(twitter.id)) + option = SelectOption(label=twitter.handle, value=str(twitter.id)) options.append(option) - select = create_select(options=options, custom_id="to_delete", min_values=1, max_values=len(twitters)) + select = Select( + options=options, custom_id="to_delete", min_values=1, max_values=len(twitters) + ) - components = [create_actionrow(select)] + components = [ActionRow(select)] block = "\n".join(x.handle for x in twitters) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" @@ -166,52 +126,58 @@ class TwitterCog(commands.Cog): ) try: - context = await wait_for_component( - self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5 + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, + timeout=60 * 5, ) - for to_delete in context.selected_options: + for to_delete in context.context.values: _ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete() for row in components: - for component in row["components"]: - component["disabled"] = True - block = "\n".join(handlemap[x] for x in context.selected_options) - await context.edit_origin(content=f"Unfollowed the following:\n```\n{block}\n```", components=components) + for component in row.components: + component.disabled = True + + block = "\n".join(handlemap[x] for x in context.context.values) + await context.context.edit_origin( + content=f"Unfollowed the following:\n```\n{block}\n```", components=components + ) except asyncio.TimeoutError: for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True await message.edit(components=components) - @cog_ext.cog_subcommand( - base="twitter", + @slash_command( + name="twitter", sub_cmd_name="retweets", description="Modify followed Twitter accounts" + ) + @slash_option( name="retweets", - description="Modify followed Twitter accounts", - options=[ - create_option( - name="retweets", - description="Mirror re-tweets?", - option_type=COptionType.STRING, - required=True, - choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")], - ), + description="Mirror re-tweets?", + opt_type=OptionTypes.STRING, + required=False, + choices=[ + SlashCommandChoice(name="Yes", value="Yes"), + SlashCommandChoice(name="No", value="No"), ], ) - @admin_or_permissions(manage_guild=True) - async def _twitter_modify(self, ctx: SlashContext, retweets: str) -> None: + @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) if not twitters: - await ctx.send("You need to follow a Twitter account first", hidden=True) + await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] for twitter in twitters: - option = create_select_option(label=twitter.handle, value=str(twitter.id)) + option = SelectOption(label=twitter.handle, value=str(twitter.id)) options.append(option) - select = create_select(options=options, custom_id="to_update", min_values=1, max_values=len(twitters)) + select = Select( + options=options, custom_id="to_update", min_values=1, max_values=len(twitters) + ) - components = [create_actionrow(select)] + components = [ActionRow(select)] block = "\n".join(x.handle for x in twitters) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" @@ -220,30 +186,38 @@ class TwitterCog(commands.Cog): ) try: - context = await wait_for_component( - self.bot, check=lambda x: ctx.author.id == x.author.id, messages=message, timeout=60 * 5 + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.author.id, + messages=message, + timeout=60 * 5, ) + handlemap = {str(x.id): x.handle for x in twitters} - for to_update in context.selected_options: + for to_update in context.context.values: t = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first() t.retweets = retweets t.save() + for row in components: - for component in row["components"]: - component["disabled"] = True - block = "\n".join(handlemap[x] for x in context.selected_options) - await context.edit_origin( - content=f"{'Unfollowed' if not retweets else 'Followed'} retweets from the following:" - f"\n```\n{block}\n```", + for component in row.components: + component.disabled = True + + block = "\n".join(handlemap[x] for x in context.context.values) + await context.context.edit_origin( + content=( + f"{'Unfollowed' if not retweets else 'Followed'} " + "retweets from the following:" + f"\n```\n{block}\n```" + ), components=components, ) except asyncio.TimeoutError: for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True await message.edit(components=components) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add TwitterCog to J.A.R.V.I.S.""" - bot.add_cog(TwitterCog(bot)) + TwitterCog(bot) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index c37da98..936c855 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,165 +1,148 @@ """J.A.R.V.I.S. Utility Cog.""" +import platform import re import secrets import string from io import BytesIO -import discord -import discord_slash import numpy as np -from discord import File, Guild, Role, User -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_choice, create_option +from dis_snek import InteractionContext, Scale, Snake, const +from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.file import File +from dis_snek.models.discord.guild import Guild +from dis_snek.models.discord.role import Role +from dis_snek.models.discord.user import User +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from PIL import Image import jarvis -from jarvis import jarvis_self from jarvis.config import get_config from jarvis.data import pigpen from jarvis.data.robotcamo import emotes, hk, names -from jarvis.utils import build_embed, convert_bytesize, get_repo_hash -from jarvis.utils.field import Field +from jarvis.utils import build_embed, get_repo_hash JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA") -class UtilCog(commands.Cog): +class UtilCog(Scale): """ Utility functions for J.A.R.V.I.S. Mostly system utility functions, but may change over time """ - def __init__(self, bot: commands.Cog): + def __init__(self, bot: Snake): self.bot = bot self.config = get_config() - @cog_ext.cog_slash( - name="status", - description="Retrieve J.A.R.V.I.S. status", - ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _status(self, ctx: SlashContext) -> None: + @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") + @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) + async def _status(self, ctx: InteractionContext) -> None: title = "J.A.R.V.I.S. Status" desc = "All systems online" - color = "#98CCDA" + color = "#3498db" fields = [] - with jarvis_self.oneshot(): - fields.append(Field("CPU Usage", jarvis_self.cpu_percent())) - fields.append( - Field( - "RAM Usage", - convert_bytesize(jarvis_self.memory_info().rss), - ) - ) - fields.append(Field("PID", jarvis_self.pid)) - fields.append(Field("discord_slash", discord_slash.__version__)) - fields.append(Field("discord.py", discord.__version__)) - fields.append(Field("Version", jarvis.__version__, False)) - fields.append(Field("Git Hash", get_repo_hash()[:7], False)) - embed = build_embed(title=title, description=desc, fields=fields, color=color) - await ctx.send(embed=embed) - @cog_ext.cog_slash( + fields.append(EmbedField(name="dis-snek", value=const.__version__)) + fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) + fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) + embed = build_embed(title=title, description=desc, fields=fields, color=color) + await ctx.send(embed=embed) + + @slash_command( name="logo", description="Get the current logo", ) - @commands.cooldown(1, 30, commands.BucketType.channel) - async def _logo(self, ctx: SlashContext) -> None: + @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) + async def _logo(self, ctx: InteractionContext) -> None: with BytesIO() as image_bytes: JARVIS_LOGO.save(image_bytes, "PNG") image_bytes.seek(0) - logo = File(image_bytes, filename="logo.png") - + logo = File(image_bytes, file_name="logo.png") await ctx.send(file=logo) - @cog_ext.cog_slash(name="rchk", description="Robot Camo HK416") - async def _rchk(self, ctx: SlashContext) -> None: + @slash_command(name="rchk", description="Robot Camo HK416") + async def _rchk(self, ctx: InteractionContext) -> None: await ctx.send(content=hk) - @cog_ext.cog_slash( + @slash_command( name="rcauto", description="Automates robot camo letters", - options=[ - create_option( - name="text", - description="Text to camo-ify", - option_type=3, - required=True, - ) - ], ) - async def _rcauto(self, ctx: SlashContext, text: str) -> None: + @slash_option( + name="text", + description="Text to camo-ify", + opt_type=OptionTypes.STRING, + required=True, + ) + async def _rcauto(self, ctx: InteractionContext, text: str) -> None: to_send = "" if len(text) == 1 and not re.match(r"^[A-Z0-9-()$@!?^'#. ]$", text.upper()): - await ctx.send("Please use ASCII characters.", hidden=True) + await ctx.send("Please use ASCII characters.", ephemeral=True) return for letter in text.upper(): if letter == " ": to_send += " " elif re.match(r"^[A-Z0-9-()$@!?^'#.]$", letter): id = emotes[letter] - if ctx.author.is_on_mobile(): - to_send += f":{names[id]}:" - else: - to_send += f"<:{names[id]}:{id}>" + to_send += f":{names[id]}:" if len(to_send) > 2000: - await ctx.send("Too long.", hidden=True) + await ctx.send("Too long.", ephemeral=True) else: await ctx.send(to_send) - @cog_ext.cog_slash( - name="avatar", - description="Get a user avatar", - options=[ - create_option( - name="user", - description="User to view avatar of", - option_type=6, - required=False, - ) - ], + @slash_command(name="avatar", description="Get a user avatar") + @slash_option( + name="user", + description="User to view avatar of", + opt_type=OptionTypes.USER, + required=False, ) - @commands.cooldown(1, 5, commands.BucketType.user) - async def _avatar(self, ctx: SlashContext, user: User = None) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=5) + async def _avatar(self, ctx: InteractionContext, user: User = None) -> None: if not user: user = ctx.author - avatar = user.avatar_url + avatar = user.display_avatar.url embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE") embed.set_image(url=avatar) - embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=avatar) + embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar) await ctx.send(embed=embed) - @cog_ext.cog_slash( + @slash_command( name="roleinfo", description="Get role info", - options=[ - create_option( - name="role", - description="Role to get info of", - option_type=8, - required=True, - ) - ], ) - async def _roleinfo(self, ctx: SlashContext, role: Role) -> None: + @slash_option( + name="role", + description="Role to get info of", + opt_type=OptionTypes.ROLE, + required=True, + ) + async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None: fields = [ - Field(name="ID", value=role.id), - Field(name="Name", value=role.name), - Field(name="Color", value=str(role.color)), - Field(name="Mention", value=f"`{role.mention}`"), - Field(name="Hoisted", value="Yes" if role.hoist else "No"), - Field(name="Position", value=str(role.position)), - Field(name="Mentionable", value="Yes" if role.mentionable else "No"), + EmbedField(name="ID", value=str(role.id), inline=True), + EmbedField(name="Name", value=role.name, inline=True), + EmbedField(name="Color", value=str(role.color.hex), inline=True), + EmbedField(name="Mention", value=f"`{role.mention}`", inline=True), + EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True), + EmbedField(name="Position", value=str(role.position), inline=True), + EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True), + EmbedField(name="Member Count", value=str(len(role.members)), inline=True), ] - embed = build_embed( title="", description="", fields=fields, - color=str(role.color), + color=role.color, timestamp=role.created_at, ) embed.set_footer(text="Role Created") @@ -170,46 +153,47 @@ class UtilCog(commands.Cog): fill = a > 0 - data[..., :-1][fill.T] = list(role.color.to_rgb()) + data[..., :-1][fill.T] = list(role.color.rgb) im = Image.fromarray(data) with BytesIO() as image_bytes: im.save(image_bytes, "PNG") image_bytes.seek(0) - color_show = File(image_bytes, filename="color_show.png") + color_show = File(image_bytes, file_name="color_show.png") await ctx.send(embed=embed, file=color_show) - @cog_ext.cog_slash( + @slash_command( name="userinfo", description="Get user info", - options=[ - create_option( - name="user", - description="User to get info of", - option_type=6, - required=False, - ) - ], ) - async def _userinfo(self, ctx: SlashContext, user: User = None) -> None: + @slash_option( + name="user", + description="User to get info of", + opt_type=OptionTypes.USER, + required=False, + ) + async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: if not user: user = ctx.author user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) - _ = user_roles.pop(-1) + format_string = "%a, %b %-d, %Y %-I:%M %p" + if platform.system() == "Windows": + format_string = "%a, %b %#d, %Y %#I:%M %p" + fields = [ - Field( + EmbedField( name="Joined", - value=user.joined_at.strftime("%a, %b %-d, %Y %-I:%M %p"), + value=user.joined_at.strftime(format_string), ), - Field( + EmbedField( name="Registered", - value=user.created_at.strftime("%a, %b %-d, %Y %-I:%M %p"), + value=user.created_at.strftime(format_string), ), - Field( + EmbedField( name=f"Roles [{len(user_roles)}]", value=" ".join([x.mention for x in user_roles]) if user_roles else "None", inline=False, @@ -220,80 +204,82 @@ class UtilCog(commands.Cog): title="", description=user.mention, fields=fields, - color=str(user_roles[0].color) if user_roles else "#FF0000", + color=str(user_roles[0].color) if user_roles else "#3498db", ) - embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=user.avatar_url) - embed.set_thumbnail(url=user.avatar_url) + embed.set_author( + name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar.url + ) + embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"ID: {user.id}") await ctx.send(embed=embed) - @cog_ext.cog_slash(name="serverinfo", description="Get server info") - async def _server_info(self, ctx: SlashContext) -> None: + @slash_command(name="serverinfo", description="Get server info") + async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild - owner = f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||" + owner = await guild.get_owner() - region = guild.region - categories = len(guild.categories) - text_channels = len(guild.text_channels) - voice_channels = len(guild.voice_channels) + owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||" + + categories = len([x for x in guild.channels if isinstance(x, GuildCategory)]) + text_channels = len([x for x in guild.channels if isinstance(x, GuildText)]) + voice_channels = len([x for x in guild.channels if isinstance(x, GuildVoice)]) + threads = len(guild.threads) members = guild.member_count roles = len(guild.roles) - role_list = ", ".join(role.name for role in guild.roles) + role_list = sorted(guild.roles, key=lambda x: x.position, reverse=True) + role_list = ", ".join(role.mention for role in role_list) fields = [ - Field(name="Owner", value=owner), - Field(name="Region", value=region), - Field(name="Channel Categories", value=categories), - Field(name="Text Channels", value=text_channels), - Field(name="Voice Channels", value=voice_channels), - Field(name="Members", value=members), - Field(name="Roles", value=roles), + EmbedField(name="Owner", value=owner, inline=True), + EmbedField(name="Channel Categories", value=str(categories), inline=True), + EmbedField(name="Text Channels", value=str(text_channels), inline=True), + EmbedField(name="Voice Channels", value=str(voice_channels), inline=True), + EmbedField(name="Threads", value=str(threads), inline=True), + EmbedField(name="Members", value=str(members), inline=True), + EmbedField(name="Roles", value=str(roles), inline=True), ] if len(role_list) < 1024: - fields.append(Field(name="Role List", value=role_list, inline=False)) + fields.append(EmbedField(name="Role List", value=role_list, inline=False)) embed = build_embed(title="", description="", fields=fields, timestamp=guild.created_at) - embed.set_author(name=guild.name, icon_url=guild.icon_url) - embed.set_thumbnail(url=guild.icon_url) + embed.set_author(name=guild.name, icon_url=guild.icon.url) + embed.set_thumbnail(url=guild.icon.url) embed.set_footer(text=f"ID: {guild.id} | Server Created") await ctx.send(embed=embed) - @cog_ext.cog_subcommand( - base="pw", - name="gen", - base_desc="Password utilites", + @slash_command( + name="pw", + sub_cmd_name="gen", description="Generate a secure password", - guild_ids=[862402786116763668], - options=[ - create_option( - name="length", - description="Password length (default 32)", - option_type=4, - required=False, - ), - create_option( - name="chars", - description="Characters to include (default last option)", - option_type=4, - required=False, - choices=[ - create_choice(name="A-Za-z", value=0), - create_choice(name="A-Fa-f0-9", value=1), - create_choice(name="A-Za-z0-9", value=2), - create_choice(name="A-Za-z0-9!@#$%^&*", value=3), - ], - ), + scopes=[862402786116763668], + ) + @slash_option( + name="length", + description="Password length (default 32)", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="chars", + description="Characters to include (default last option)", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[ + SlashCommandChoice(name="A-Za-z", value=0), + SlashCommandChoice(name="A-Fa-f0-9", value=1), + SlashCommandChoice(name="A-Za-z0-9", value=2), + SlashCommandChoice(name="A-Za-z0-9!@#$%^&*", value=3), ], ) - @commands.cooldown(1, 15, type=commands.BucketType.user) - async def _pw_gen(self, ctx: SlashContext, length: int = 32, chars: int = 3) -> None: + @cooldown(bucket=Buckets.USER, rate=1, interval=15) + async def _pw_gen(self, ctx: InteractionContext, length: int = 32, chars: int = 3) -> None: if length > 256: - await ctx.send("Please limit password to 256 characters", hidden=True) + await ctx.send("Please limit password to 256 characters", ephemeral=True) return choices = [ string.ascii_letters, @@ -307,15 +293,14 @@ class UtilCog(commands.Cog): f"Generated password:\n`{pw}`\n\n" '**WARNING: Once you press "Dismiss Message", ' "*the password is lost forever***", - hidden=True, + ephemeral=True, ) - @cog_ext.cog_slash( - name="pigpen", - description="Encode a string into pigpen", - options=[create_option(name="text", description="Text to encode", option_type=3, required=True)], + @slash_command(name="pigpen", description="Encode a string into pigpen") + @slash_option( + name="text", description="Text to encode", opt_type=OptionTypes.STRING, required=True ) - async def _pigpen(self, ctx: SlashContext, text: str) -> None: + async def _pigpen(self, ctx: InteractionContext, text: str) -> None: outp = "`" for c in text: c = c.lower() @@ -330,6 +315,6 @@ class UtilCog(commands.Cog): await ctx.send(outp[:2000]) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add UtilCog to J.A.R.V.I.S.""" - bot.add_cog(UtilCog(bot)) + UtilCog(bot) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 540e65c..2d1db38 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -1,10 +1,12 @@ """J.A.R.V.I.S. Verify Cog.""" +import asyncio from random import randint -from discord.ext import commands -from discord_slash import ComponentContext, SlashContext, cog_ext -from discord_slash.model import ButtonStyle -from discord_slash.utils import manage_components +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.models.application_commands import slash_command +from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.db.models import Setting @@ -16,36 +18,32 @@ def create_layout() -> list: for i in range(3): label = "YES" if i == yes else "NO" id = f"no_{i}" if not i == yes else "yes" - color = ButtonStyle.green if i == yes else ButtonStyle.red + color = ButtonStyles.GREEN if i == yes else ButtonStyles.RED buttons.append( - manage_components.create_button( + Button( style=color, label=label, custom_id=f"verify_button||{id}", ) ) - action_row = manage_components.spread_to_rows(*buttons, max_in_row=3) - return action_row + return spread_to_rows(*buttons, max_in_row=3) -class VerifyCog(commands.Cog): +class VerifyCog(Scale): """J.A.R.V.I.S. Verify Cog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @cog_ext.cog_slash( - name="verify", - description="Verify that you've read the rules", - ) - @commands.cooldown(1, 15, commands.BucketType.user) - async def _verify(self, ctx: SlashContext) -> None: + @slash_command(name="verify", description="Verify that you've read the rules") + @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() if not role: await ctx.send("This guild has not enabled verification", delete_after=5) return - if ctx.guild.get_role(role.value) in ctx.author.roles: + if await ctx.guild.get_role(role.value) in ctx.author.roles: await ctx.send("You are already verified.", delete_after=5) return components = create_layout() @@ -53,40 +51,41 @@ class VerifyCog(commands.Cog): content=f"{ctx.author.mention}, please press the button that says `YES`.", components=components, ) - await message.delete(delay=15) - @cog_ext.cog_component(components=create_layout()) - async def _process(self, ctx: ComponentContext) -> None: - await ctx.defer(edit_origin=True) try: - if ctx.author.id != ctx.origin_message.mentions[0].id: - return - except Exception: - return - correct = ctx.custom_id.split("||")[-1] == "yes" - if correct: - components = ctx.origin_message.components - for c in components: - for c2 in c["components"]: - c2["disabled"] = True - setting = Setting.objects(guild=ctx.guild.id, setting="verified").first() - role = 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() - if setting: - role = ctx.guild.get_role(setting.value) - await ctx.author.remove_roles(role, reason="Verification passed") - await ctx.edit_origin( - content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", - components=manage_components.spread_to_rows(*components, max_in_row=5), - ) - await ctx.origin_message.delete(delay=5) - else: - await ctx.edit_origin( - content=f"{ctx.author.mention}, incorrect. Please press the button that says `YES`", + context = await self.bot.wait_for_component( + messages=message, check=lambda x: ctx.author.id == x.author.id, timeout=30 ) + correct = context.context.custom_id.split("||")[-1] == "yes" + if correct: + for row in components: + for component in row.components: + component.disabled = True + setting = Setting.objects(guild=ctx.guild.id, setting="verified").first() + 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() + if setting: + role = await ctx.guild.get_role(setting.value) + await ctx.author.remove_roles(role, reason="Verification passed") -def setup(bot: commands.Bot) -> None: + await context.context.edit_origin( + content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", + components=components, + ) + await context.context.message.delete(delay=5) + else: + await context.context.edit_origin( + content=( + f"{ctx.author.mention}, incorrect. " + "Please press the button that says `YES`" + ) + ) + except asyncio.TimeoutError: + await message.delete(delay=30) + + +def setup(bot: Snake) -> None: """Add VerifyCog to J.A.R.V.I.S.""" - bot.add_cog(VerifyCog(bot)) + VerifyCog(bot) diff --git a/jarvis/config.py b/jarvis/config.py index 0338b29..c2b9983 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,4 +1,6 @@ """Load the config for J.A.R.V.I.S.""" +import os + from pymongo import MongoClient from yaml import load @@ -27,6 +29,7 @@ class Config(object): logo: str, mongo: dict, urls: dict, + sync: bool = False, log_level: str = "WARNING", cogs: list = None, events: bool = True, @@ -46,6 +49,7 @@ class Config(object): self.max_messages = max_messages self.gitlab_token = gitlab_token self.twitter = twitter + self.sync = sync or os.environ.get("SYNC_COMMANDS", False) self.__db_loaded = False self.__mongo = MongoClient(**self.mongo["connect"]) @@ -61,8 +65,7 @@ class Config(object): @classmethod def from_yaml(cls, y: dict) -> "Config": """Load the yaml config file.""" - instance = cls(**y) - return instance + return cls(**y) def get_config(path: str = "config.yaml") -> Config: diff --git a/jarvis/db/models.py b/jarvis/db/models.py index 2449012..a99cb45 100644 --- a/jarvis/db/models.py +++ b/jarvis/db/models.py @@ -222,6 +222,7 @@ 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) @@ -229,6 +230,7 @@ class Twitter(Document): retweets = BooleanField(default=True) admin = SnowflakeField(required=True) created_at = DateTimeField(default=datetime.utcnow) + last_sync = DateTimeField() meta = {"db_alias": "main"} diff --git a/jarvis/events/guild.py b/jarvis/events/guild.py deleted file mode 100644 index 5f7dc62..0000000 --- a/jarvis/events/guild.py +++ /dev/null @@ -1,36 +0,0 @@ -"""J.A.R.V.I.S. guild event handler.""" -import asyncio - -from discord import Guild -from discord.ext.commands import Bot -from discord.utils import find - -from jarvis.db.models import Setting - - -class GuildEventHandler(object): - """J.A.R.V.I.S. guild event handler.""" - - def __init__(self, bot: Bot): - self.bot = bot - self.bot.add_listener(self.on_guild_join) - - async def on_guild_join(self, guild: Guild) -> None: - """Handle on_guild_join event.""" - general = find(lambda x: x.name == "general", guild.channels) - if general and general.permissions_for(guild.me).send_messages: - user = self.bot.user - await general.send( - f"Allow me to introduce myself. I am {user.mention}, a virtual " - "artificial intelligence, and I'm here to assist you with a " - "variety of tasks as best I can, " - "24 hours a day, seven days a week." - ) - await asyncio.sleep(1) - await general.send("Importing all preferences from home interface...") - - # Set some default settings - _ = Setting(guild=guild.id, setting="massmention", value=5).save() - _ = Setting(guild=guild.id, setting="noinvite", value=True).save() - - await general.send("Systems are now fully operational") diff --git a/jarvis/events/member.py b/jarvis/events/member.py index 8dec15e..ee1043f 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -1,6 +1,6 @@ """J.A.R.V.I.S. Member event handler.""" -from discord import Member -from discord.ext.commands import Bot +from dis_snek import Snake, listen +from dis_snek.models.discord.user import Member from jarvis.db.models import Mute, Setting @@ -8,10 +8,11 @@ from jarvis.db.models import Mute, Setting class MemberEventHandler(object): """J.A.R.V.I.S. Member event handler.""" - def __init__(self, bot: Bot): + 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 diff --git a/jarvis/events/message.py b/jarvis/events/message.py index 2cfea81..f62dc4a 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -1,17 +1,17 @@ """J.A.R.V.I.S. Message event handler.""" import re -from discord import DMChannel, Message -from discord.ext.commands import Bot -from discord.utils import find +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.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 -from jarvis.utils.field import Field +from jarvis.utils import build_embed, find invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", + r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) @@ -19,7 +19,7 @@ invites = re.compile( class MessageEventHandler(object): """J.A.R.V.I.S. Message event handler.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snake): self.bot = bot self.bot.add_listener(self.on_message) self.bot.add_listener(self.on_message_edit) @@ -46,7 +46,7 @@ class MessageEventHandler(object): 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" + content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 ) content = re.sub(r"\s+", "", message.content) match = invites.search(content) @@ -72,10 +72,10 @@ class MessageEventHandler(object): user=message.author.id, ).save() fields = [ - Field( - "Reason", - "Sent an invite link", - False, + EmbedField( + name="Reason", + value="Sent an invite link", + inline=False, ) ] embed = build_embed( @@ -85,9 +85,11 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 ) - embed.set_footer(text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}") await message.channel.send(embed=embed) async def massmention(self, message: Message) -> None: @@ -99,7 +101,8 @@ class MessageEventHandler(object): if ( massmention and massmention.value > 0 # noqa: W503 - and len(message.mentions) - (1 if message.author in message.mentions else 0) # noqa: W503 + and len(message.mentions) # noqa: W503 + - (1 if message.author in message.mentions else 0) # noqa: W503 > massmention.value # noqa: W503 ): _ = Warning( @@ -110,7 +113,7 @@ class MessageEventHandler(object): reason="Mass Mention", user=message.author.id, ).save() - fields = [Field("Reason", "Mass Mention", False)] + fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] embed = build_embed( title="Warning", description=f"{message.author.mention} has been warned", @@ -118,9 +121,11 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" ) - 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: @@ -173,10 +178,10 @@ class MessageEventHandler(object): user=message.author.id, ).save() fields = [ - Field( - "Reason", - "Pinged a blocked role/user with a blocked role", - False, + EmbedField( + name="Reason", + value="Pinged a blocked role/user with a blocked role", + inline=False, ) ] embed = build_embed( @@ -186,11 +191,14 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" ) - 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: @@ -200,6 +208,7 @@ class MessageEventHandler(object): 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: @@ -208,3 +217,10 @@ 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/tasks/__init__.py b/jarvis/tasks/__init__.py index 3da9656..9825337 100644 --- a/jarvis/tasks/__init__.py +++ b/jarvis/tasks/__init__.py @@ -1,10 +1,10 @@ """J.A.R.V.I.S. background task handlers.""" -from jarvis.tasks import unban, unlock, unmute, unwarn +from jarvis.tasks import twitter, unban, unlock, unwarn def init() -> None: """Start the background task handlers.""" unban.unban.start() unlock.unlock.start() - unmute.unmute.start() unwarn.unwarn.start() + twitter.tweets.start() diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py new file mode 100644 index 0000000..4c65e8e --- /dev/null +++ b/jarvis/tasks/reminder.py @@ -0,0 +1,45 @@ +"""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 + +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: + if reminder.remind_at <= datetime.utcnow(): + user = await jarvis.jarvis.fetch_user(reminder.user) + if not user: + reminder.delete() + continue + embed = build_embed( + title="You have a reminder", + description=reminder.message, + fields=[], + ) + embed.set_author( + name=user.name + "#" + user.discriminator, icon_url=user.display_avatar.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: + await channel.send(f"{user.mention}", embed=embed) + 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) diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py new file mode 100644 index 0000000..46638f5 --- /dev/null +++ b/jarvis/tasks/twitter.py @@ -0,0 +1,70 @@ +"""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 + +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.""" + guild_cache = {} + channel_cache = {} + twitters = Twitter.objects(active=True) + 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() + + 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() + + 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.get_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.last_tweet = newest.id + twitter.save() + 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 45c0a67..f5b2849 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -1,7 +1,9 @@ """J.A.R.V.I.S. unban background task handler.""" +from asyncio import to_thread from datetime import datetime, timedelta -from discord.ext.tasks import loop +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger import jarvis from jarvis.config import get_config @@ -10,17 +12,18 @@ from jarvis.db.models import Ban, Unban jarvis_id = get_config().client_id -@loop(minutes=10) -async def unban() -> None: - """J.A.R.V.I.S. unban background task.""" +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): - guild = await jarvis.jarvis.fetch_guild(ban.guild) - user = await jarvis.jarvis.fetch_user(ban.user) + if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta( + minutes=10 + ): + guild = await jarvis.jarvis.get_guild(ban.guild) + user = await jarvis.jarvis.get_user(ban.user) if user: - guild.unban(user) + await guild.unban(user=user, reason="Ban expired") ban.active = False ban.save() unbans.append( @@ -34,4 +37,10 @@ async def unban() -> None: ) ) if unbans: - Ban.objects().insert(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) diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py index 19cf83b..2d8793f 100644 --- a/jarvis/tasks/unlock.py +++ b/jarvis/tasks/unlock.py @@ -1,25 +1,37 @@ """J.A.R.V.I.S. unlock background task handler.""" +from asyncio import to_thread from datetime import datetime, timedelta -from discord.ext.tasks import loop +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger import jarvis from jarvis.db.models import Lock -@loop(minutes=1) +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.get_guild(lock.guild) + channel = await jarvis.jarvis.get_guild(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.""" - locks = Lock.objects(active=True) - for lock in locks: - if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow(): - guild = await jarvis.jarvis.fetch_guild(lock.guild) - channel = await jarvis.jarvis.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() + await to_thread(_unlock) diff --git a/jarvis/tasks/unmute.py b/jarvis/tasks/unmute.py deleted file mode 100644 index 6b24741..0000000 --- a/jarvis/tasks/unmute.py +++ /dev/null @@ -1,27 +0,0 @@ -"""J.A.R.V.I.S. unmute background task handler.""" -from datetime import datetime, timedelta - -from discord.ext.tasks import loop - -import jarvis -from jarvis.db.models import Mute, Setting - - -@loop(minutes=1) -async def unmute() -> None: - """J.A.R.V.I.S. unmute background task.""" - mutes = Mute.objects(duration__gt=0, active=True) - mute_roles = Setting.objects(setting="mute") - for mute in mutes: - if mute.created_at + timedelta(minutes=mute.duration) < datetime.utcnow(): - mute_role = [x.value for x in mute_roles if x.guild == mute.guild][0] - guild = await jarvis.jarvis.fetch_guild(mute.guild) - role = guild.get_role(mute_role) - user = await guild.fetch_member(mute.user) - if user: - if role in user.roles: - await user.remove_roles(role, reason="Mute expired") - - # Objects can't handle bulk_write, so handle it via raw methods - mute.active = False - mute.save diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py index 6b0dd91..0af40a1 100644 --- a/jarvis/tasks/unwarn.py +++ b/jarvis/tasks/unwarn.py @@ -1,16 +1,23 @@ """J.A.R.V.I.S. unwarn background task handler.""" +from asyncio import to_thread from datetime import datetime, timedelta -from discord.ext.tasks import loop +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger from jarvis.db.models import Warning -@loop(hours=1) -async def unwarn() -> None: - """J.A.R.V.I.S. unwarn background task.""" +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() + + +@Task.create(IntervalTrigger(hours=1)) +async def unwarn() -> None: + """J.A.R.V.I.S. unwarn background task.""" + await to_thread(_unwarn) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 2887a0d..11318e1 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,10 +1,10 @@ """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 discord import Color, Embed, Message -from discord.ext import commands +from dis_snek.models.discord.embed import Embed import jarvis.cogs import jarvis.db @@ -12,6 +12,31 @@ from jarvis.config import get_config __all__ = ["field", "db", "cachecog", "permissions"] +T = TypeVar("T") + + +def build_embed( + title: str, + description: str, + fields: list, + color: str = "#FF0000", + timestamp: datetime = None, + **kwargs: dict, +) -> Embed: + """Embed builder utility function.""" + if not timestamp: + timestamp = datetime.now() + embed = Embed( + title=title, + description=description, + color=color, + timestamp=timestamp, + **kwargs, + ) + for field in fields: + embed.add_field(**field.to_dict()) + return embed + def convert_bytesize(b: int) -> str: """Convert bytes amount to human readable.""" @@ -34,15 +59,6 @@ def unconvert_bytesize(size: int, ending: str) -> int: return round(size * (1024 ** sizes.index(ending))) -def get_prefix(bot: commands.Bot, message: Message) -> list: - """Get bot prefixes.""" - prefixes = ["!", "-", "%"] - # if not message.guild: - # return "?" - - return commands.when_mentioned_or(*prefixes)(bot, message) - - def get_extensions(path: str = jarvis.cogs.__path__) -> list: """Get J.A.R.V.I.S. cogs.""" config = get_config() @@ -50,36 +66,6 @@ def get_extensions(path: str = jarvis.cogs.__path__) -> list: return ["jarvis.cogs.{}".format(x) for x in vals] -def parse_color_hex(hex: str) -> Color: - """Convert a hex color to a d.py Color.""" - hex = hex.lstrip("#") - rgb = tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)) # noqa: E203 - return Color.from_rgb(*rgb) - - -def build_embed( - title: str, - description: str, - fields: list, - color: str = "#FF0000", - timestamp: datetime = None, - **kwargs: dict, -) -> Embed: - """Embed builder utility function.""" - if not timestamp: - timestamp = datetime.utcnow() - embed = Embed( - title=title, - description=description, - color=parse_color_hex(color), - timestamp=timestamp, - **kwargs, - ) - for field in fields: - embed.add_field(**field.to_dict()) - return embed - - def update() -> int: """J.A.R.V.I.S. update utility.""" repo = git.Repo(".") @@ -99,3 +85,103 @@ 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 + + """ + matches = [] + for el in sequence: + if predicate(el): + matches.append(el) + return matches + + +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 12c043d..9497bac 100644 --- a/jarvis/utils/cachecog.py +++ b/jarvis/utils/cachecog.py @@ -1,21 +1,22 @@ """Cog wrapper for command caching.""" from datetime import datetime, timedelta -from discord.ext import commands -from discord.ext.tasks import loop -from discord.utils import find -from discord_slash import SlashContext +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger + +from jarvis.utils import find -class CacheCog(commands.Cog): +class CacheCog(Scale): """Cog wrapper for command caching.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot self.cache = {} self._expire_interaction.start() - def check_cache(self, ctx: SlashContext, **kwargs: dict) -> dict: + def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: """Check the cache.""" if not kwargs: kwargs = {} @@ -27,7 +28,7 @@ class CacheCog(commands.Cog): self.cache.values(), ) - @loop(minutes=1) + @Task.create(IntervalTrigger(minutes=1)) async def _expire_interaction(self) -> None: keys = list(self.cache.keys()) for key in keys: diff --git a/jarvis/utils/field.py b/jarvis/utils/field.py deleted file mode 100644 index 8339f65..0000000 --- a/jarvis/utils/field.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Embed field helper.""" -from dataclasses import dataclass -from typing import Any - - -@dataclass -class Field: - """Embed Field.""" - - name: Any - value: Any - inline: bool = True - - def to_dict(self) -> dict: - """Convert Field to d.py field dict.""" - return {"name": self.name, "value": self.value, "inline": self.inline} diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index fc1c86b..a20b686 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -1,5 +1,5 @@ """Permissions wrappers.""" -from discord.ext import commands +from dis_snek import InteractionContext, Permissions from jarvis.config import get_config @@ -7,22 +7,23 @@ from jarvis.config import get_config def user_is_bot_admin() -> bool: """Check if a user is a J.A.R.V.I.S. admin.""" - def predicate(ctx: commands.Context) -> bool: + async def predicate(ctx: InteractionContext) -> bool: """Command check predicate.""" if getattr(get_config(), "admins", None): return ctx.author.id in get_config().admins else: return False - return commands.check(predicate) + return predicate -def admin_or_permissions(**perms: dict) -> bool: +def admin_or_permissions(*perms: list) -> bool: """Check if a user is an admin or has other perms.""" - original = commands.has_permissions(**perms).predicate - async def extended_check(ctx: commands.Context) -> bool: + async def predicate(ctx: InteractionContext) -> bool: """Extended check predicate.""" # noqa: D401 - return await commands.has_permissions(administrator=True).predicate(ctx) or await original(ctx) + is_admin = ctx.author.has_permission(Permissions.ADMINISTRATOR) + has_other = any(ctx.author.has_permission(perm) for perm in perms) + return is_admin or has_other - return commands.check(extended_check) + return predicate diff --git a/jarvis_small.png b/jarvis_small.png index b2610b3..26d4bd9 100644 Binary files a/jarvis_small.png and b/jarvis_small.png differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f59cc4b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1568 @@ +[[package]] +name = "aiohttp" +version = "3.8.1" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotli", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "astroid" +version = "2.9.3" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = ">=1.11,<1.14" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "autopep8" +version = "1.6.0" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pycodestyle = ">=2.8.0" +toml = "*" + +[[package]] +name = "black" +version = "22.1.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = ">=1.1.0" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.11" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "dis-snek" +version = "5.0.0" +description = "An API wrapper for Discord filled with snakes" +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +aiohttp = "*" +attrs = "*" +tomli = "*" + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "frozenlist" +version = "1.3.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "gitdb" +version = "4.0.9" +description = "Git Object Database" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.26" +description = "GitPython is a python library used to interact with Git repositories" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "jedi" +version = "0.18.1" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.7.1" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mongoengine" +version = "0.23.1" +description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pymongo = ">=3.4,<4.0" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "numpy" +version = "1.22.1" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "oauthlib" +version = "3.2.0" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "opencv-python" +version = "4.5.5.62" +description = "Wrapper package for OpenCV python bindings." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\" or python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.14.5", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] + +[[package]] +name = "orjson" +version = "3.6.6" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "pillow" +version = "9.0.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.4.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psutil" +version = "5.9.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pydocstyle" +version = "6.1.1" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +snowballstemmer = "*" + +[package.extras] +toml = ["toml"] + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pylint" +version = "2.12.2" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +astroid = ">=2.9.0,<2.10" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" +toml = ">=0.9.2" + +[[package]] +name = "pymongo" +version = "3.12.3" +description = "Python driver for MongoDB " +category = "main" +optional = false +python-versions = "*" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] +gssapi = ["pykerberos"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] +snappy = ["python-snappy"] +srv = ["dnspython (>=1.16.0,<1.17.0)"] +tls = ["ipaddress"] +zstd = ["zstandard"] + +[[package]] +name = "python-gitlab" +version = "3.1.1" +description = "Interact with GitLab API" +category = "main" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +requests = ">=2.25.0" +requests-toolbelt = ">=0.9.1" + +[package.extras] +autocompletion = ["argcomplete (>=1.10.0,<3)"] +yaml = ["PyYaml (>=5.2)"] + +[[package]] +name = "python-lsp-jsonrpc" +version = "1.0.0" +description = "JSON RPC 2.0 server library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ujson = ">=3.0.0" + +[package.extras] +test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] + +[[package]] +name = "python-lsp-server" +version = "1.3.3" +description = "Python Language Server for the Language Server Protocol" +category = "dev" +optional = false +python-versions = ">=3.6" + +[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 = "*" +pycodestyle = {version = ">=2.8.0,<2.9.0", optional = true, markers = "extra == \"all\""} +pydocstyle = {version = ">=2.0.0", optional = true, markers = "extra == \"all\""} +pyflakes = {version = ">=2.4.0,<2.5.0", optional = true, markers = "extra == \"all\""} +pylint = {version = ">=2.5.0", optional = true, markers = "extra == \"all\""} +python-lsp-jsonrpc = ">=1.0.0" +rope = {version = ">=0.10.5", optional = true, markers = "extra == \"all\""} +ujson = ">=3.0.0" +yapf = {version = "*", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["autopep8 (>=1.6.0,<1.7.0)", "flake8 (>=4.0.0,<4.1.0)", "mccabe (>=0.6.0,<0.7.0)", "pycodestyle (>=2.8.0,<2.9.0)", "pydocstyle (>=2.0.0)", "pyflakes (>=2.4.0,<2.5.0)", "pylint (>=2.5.0)", "rope (>=0.10.5)", "yapf"] +autopep8 = ["autopep8 (>=1.6.0,<1.7.0)"] +flake8 = ["flake8 (>=4.0.0,<4.1.0)"] +mccabe = ["mccabe (>=0.6.0,<0.7.0)"] +pycodestyle = ["pycodestyle (>=2.8.0,<2.9.0)"] +pydocstyle = ["pydocstyle (>=2.0.0)"] +pyflakes = ["pyflakes (>=2.4.0,<2.5.0)"] +pylint = ["pylint (>=2.5.0)"] +rope = ["rope (>0.10.5)"] +test = ["pylint (>=2.5.0)", "pytest", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5", "flaky"] +yapf = ["yapf"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "requests-toolbelt" +version = "0.9.1" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rope" +version = "0.22.0" +description = "a python refactoring library..." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["build", "pytest", "pytest-timeout"] + +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.0" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tweepy" +version = "4.5.0" +description = "Twitter library for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +requests = ">=2.27.0,<3" +requests-oauthlib = ">=1.0.0,<2" + +[package.extras] +async = ["aiohttp (>=3.7.3,<4)", "oauthlib (>=3.1.0,<4)"] +dev = ["coveralls (>=2.1.0)", "tox (>=3.14.0)"] +socks = ["requests[socks] (>=2.27.0,<3)"] +test = ["vcrpy (>=1.10.3)"] + +[[package]] +name = "ujson" +version = "5.1.0" +description = "Ultra fast JSON encoder and decoder for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "ulid-py" +version = "1.1.0" +description = "Universally Unique Lexicographically Sortable Identifier" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.8" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wrapt" +version = "1.13.3" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "yapf" +version = "0.32.0" +description = "A formatter for Python code." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "yarl" +version = "1.7.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "8a1e6e29ff70363abddad36082a494c4ce1f9cc672fe7aff30b6d5b596d50dac" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, +] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] +astroid = [ + {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, + {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +autopep8 = [ + {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, + {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, +] +black = [ + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {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"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +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-5.0.0.tar.gz", hash = "sha256:cc733b510d6b20523a8067f19b6d9b99804c13e37d78cd6e0fc401098adbca27"}, + {file = "dis_snek-5.0.0-py3-none-any.whl", hash = "sha256:d1d50ba468ad6b0788e9281eb9d83f6eb2f8d964c1212ccd0e3fb33295462263"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +frozenlist = [ + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, + {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, + {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, + {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, + {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, + {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, + {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, +] +gitdb = [ + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {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"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +jedi = [ + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mongoengine = [ + {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, + {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, +] +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"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +numpy = [ + {file = "numpy-1.22.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"}, +] +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"}, +] +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"}, +] +parso = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pillow = [ + {file = "Pillow-9.0.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"}, +] +platformdirs = [ + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +psutil = [ + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, + {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2"}, + {file = "psutil-5.9.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd"}, + {file = "psutil-5.9.0-cp27-none-win32.whl", hash = "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3"}, + {file = "psutil-5.9.0-cp27-none-win_amd64.whl", hash = "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c"}, + {file = "psutil-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"}, + {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, + {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, + {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, + {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, + {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, + {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, + {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, + {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, + {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, + {file = "psutil-5.9.0-cp37-cp37m-win32.whl", hash = "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9"}, + {file = "psutil-5.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4"}, + {file = "psutil-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d"}, + {file = "psutil-5.9.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a"}, + {file = "psutil-5.9.0-cp38-cp38-win32.whl", hash = "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666"}, + {file = "psutil-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841"}, + {file = "psutil-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07"}, + {file = "psutil-5.9.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d"}, + {file = "psutil-5.9.0-cp39-cp39-win32.whl", hash = "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845"}, + {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, + {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pydocstyle = [ + {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, + {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pylint = [ + {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, + {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, +] +pymongo = [ + {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, + {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, + {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:8c7ad5cab282f53b9d78d51504330d1c88c83fbe187e472c07e6908a0293142e"}, + {file = "pymongo-3.12.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a766157b195a897c64945d4ff87b050bb0e763bb78f3964e996378621c703b00"}, + {file = "pymongo-3.12.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c8d6bf6fcd42cde2f02efb8126812a010c297eacefcd090a609639d2aeda6185"}, + {file = "pymongo-3.12.3-cp27-cp27m-win32.whl", hash = "sha256:5fdffb0cfeb4dc8646a5381d32ec981ae8472f29c695bf09e8f7a8edb2db12ca"}, + {file = "pymongo-3.12.3-cp27-cp27m-win_amd64.whl", hash = "sha256:648fcfd8e019b122b7be0e26830a3a2224d57c3e934f19c1e53a77b8380e6675"}, + {file = "pymongo-3.12.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3f0ac6e0203bd88863649e6ed9c7cfe53afab304bc8225f2597c4c0a74e4d1f0"}, + {file = "pymongo-3.12.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:71c0db2c313ea8a80825fb61b7826b8015874aec29ee6364ade5cb774fe4511b"}, + {file = "pymongo-3.12.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b779e87300635b8075e8d5cfd4fdf7f46078cd7610c381d956bca5556bb8f97"}, + {file = "pymongo-3.12.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:351a2efe1c9566c348ad0076f4bf541f4905a0ebe2d271f112f60852575f3c16"}, + {file = "pymongo-3.12.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a02313e71b7c370c43056f6b16c45effbb2d29a44d24403a3d5ba6ed322fa3f"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux1_i686.whl", hash = "sha256:d3082e5c4d7b388792124f5e805b469109e58f1ab1eb1fbd8b998e8ab766ffb7"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:514e78d20d8382d5b97f32b20c83d1d0452c302c9a135f0a9022236eb9940fda"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:b1b5be40ebf52c3c67ee547e2c4435ed5bc6352f38d23e394520b686641a6be4"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:58db209da08a502ce6948841d522dcec80921d714024354153d00b054571993c"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:5296e5e69243ffd76bd919854c4da6630ae52e46175c804bc4c0e050d937b705"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:51d1d061df3995c2332ae78f036492cc188cb3da8ef122caeab3631a67bb477e"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b974b7f49d65a16ca1435bc1c25a681bb7d630509dd23b2e819ed36da0b7f"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e099b79ccf7c40f18b149a64d3d10639980035f9ceb223169dd806ff1bb0d9cc"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27e5ea64332385385b75414888ce9d1a9806be8616d7cef4ef409f4f256c6d06"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed7d11330e443aeecab23866055e08a5a536c95d2c25333aeb441af2dbac38d2"}, + {file = "pymongo-3.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93111fd4e08fa889c126aa8baf5c009a941880a539c87672e04583286517450a"}, + {file = "pymongo-3.12.3-cp310-cp310-win32.whl", hash = "sha256:2301051701b27aff2cbdf83fae22b7ca883c9563dfd088033267291b46196643"}, + {file = "pymongo-3.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:c7e8221278e5f9e2b6d3893cfc3a3e46c017161a57bb0e6f244826e4cee97916"}, + {file = "pymongo-3.12.3-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:7b4a9fcd95e978cd3c96cdc2096aa54705266551422cf0883c12a4044def31c6"}, + {file = "pymongo-3.12.3-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:06b64cdf5121f86b78a84e61b8f899b6988732a8d304b503ea1f94a676221c06"}, + {file = "pymongo-3.12.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:c8f7dd025cb0bf19e2f60a64dfc24b513c8330e0cfe4a34ccf941eafd6194d9e"}, + {file = "pymongo-3.12.3-cp34-cp34m-win32.whl", hash = "sha256:ab23b0545ec71ea346bf50a5d376d674f56205b729980eaa62cdb7871805014b"}, + {file = "pymongo-3.12.3-cp34-cp34m-win_amd64.whl", hash = "sha256:1b5cb75d2642ff7db823f509641f143f752c0d1ab03166cafea1e42e50469834"}, + {file = "pymongo-3.12.3-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:fc2048d13ff427605fea328cbe5369dce549b8c7657b0e22051a5b8831170af6"}, + {file = "pymongo-3.12.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c5f83bb59d0ff60c6fdb1f8a7b0288fbc4640b1f0fd56f5ae2387749c35d34e3"}, + {file = "pymongo-3.12.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6632b1c63d58cddc72f43ab9f17267354ddce563dd5e11eadabd222dcc808808"}, + {file = "pymongo-3.12.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fedad05147b40ff8a93fcd016c421e6c159f149a2a481cfa0b94bfa3e473bab"}, + {file = "pymongo-3.12.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:208a61db8b8b647fb5b1ff3b52b4ed6dbced01eac3b61009958adb203596ee99"}, + {file = "pymongo-3.12.3-cp35-cp35m-win32.whl", hash = "sha256:3100a2352bdded6232b385ceda0c0a4624598c517d52c2d8cf014b7abbebd84d"}, + {file = "pymongo-3.12.3-cp35-cp35m-win_amd64.whl", hash = "sha256:3492ae1f97209c66af70e863e6420e6301cecb0a51a5efa701058aa73a8ca29e"}, + {file = "pymongo-3.12.3-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:87e18f29bac4a6be76a30e74de9c9005475e27100acf0830679420ce1fd9a6fd"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b3e08aef4ea05afbc0a70cd23c13684e7f5e074f02450964ec5cfa1c759d33d2"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e66b3c9f8b89d4fd58a59c04fdbf10602a17c914fbaaa5e6ea593f1d54b06362"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5d67dbc8da2dac1644d71c1839d12d12aa333e266a9964d5b1a49feed036bc94"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:a351986d6c9006308f163c359ced40f80b6cffb42069f3e569b979829951038d"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5296669bff390135528001b4e48d33a7acaffcd361d98659628ece7f282f11aa"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:9d5b66d457d2c5739c184a777455c8fde7ab3600a56d8bbebecf64f7c55169e1"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:1c771f1a8b3cd2d697baaf57e9cfa4ae42371cacfbea42ea01d9577c06d92f96"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81a3ebc33b1367f301d1c8eda57eec4868e951504986d5d3fe437479dcdac5b2"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf113a46d81cff0559d57aa66ffa473d57d1a9496f97426318b6b5b14fdec1c"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64b9122be1c404ce4eb367ad609b590394587a676d84bfed8e03c3ce76d70560"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c6c71e198b36f0f0dfe354f06d3655ecfa30d69493a1da125a9a54668aad652"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33ab8c031f788609924e329003088831045f683931932a52a361d4a955b7dce2"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2b4c95c47fb81b19ea77dc1c50d23af3eba87c9628fcc2e03d44124a3d336ea"}, + {file = "pymongo-3.12.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4e0a3ea7fd01cf0a36509f320226bd8491e0f448f00b8cb89f601c109f6874e1"}, + {file = "pymongo-3.12.3-cp36-cp36m-win32.whl", hash = "sha256:dfec57f15f53d677b8e4535695ff3f37df7f8fe431f2efa8c3c8c4025b53d1eb"}, + {file = "pymongo-3.12.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c22591cff80188dd8543be0b559d0c807f7288bd353dc0bcfe539b4588b3a5cd"}, + {file = "pymongo-3.12.3-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:7738147cd9dbd6d18d5593b3491b4620e13b61de975fd737283e4ad6c255c273"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:be1f10145f7ea76e3e836fdc5c8429c605675bdcddb0bca9725ee6e26874c00c"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:295a5beaecb7bf054c1c6a28749ed72b19f4d4b61edcd8a0815d892424baf780"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:320f8734553c50cffe8a8e1ae36dfc7d7be1941c047489db20a814d2a170d7b5"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:5d20072d81cbfdd8e15e6a0c91fc7e3a4948c71e0adebfc67d3b4bcbe8602711"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:2c46a0afef69d61938a6fe32c3afd75b91dec3ab3056085dc72abbeedcc94166"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:5f530f35e1a57d4360eddcbed6945aecdaee2a491cd3f17025e7b5f2eea88ee7"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:6526933760ee1e6090db808f1690a111ec409699c1990efc96f134d26925c37f"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95d15cf81cd2fb926f2a6151a9f94c7aacc102b415e72bc0e040e29332b6731c"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d52a70350ec3dfc39b513df12b03b7f4c8f8ec6873bbf958299999db7b05eb1"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9252c991e8176b5a2fa574c5ab9a841679e315f6e576eb7cf0bd958f3e39b0ad"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:145d78c345a38011497e55aff22c0f8edd40ee676a6810f7e69563d68a125e83"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8e0a086dbbee406cc6f603931dfe54d1cb2fba585758e06a2de01037784b737"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f6d5443104f89a840250087863c91484a72f254574848e951d1bdd7d8b2ce7c9"}, + {file = "pymongo-3.12.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6f93dbfa5a461107bc3f5026e0d5180499e13379e9404f07a9f79eb5e9e1303d"}, + {file = "pymongo-3.12.3-cp37-cp37m-win32.whl", hash = "sha256:c9d212e2af72d5c8d082775a43eb726520e95bf1c84826440f74225843975136"}, + {file = "pymongo-3.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:320a1fe403dd83a35709fcf01083d14bc1462e9789b711201349a9158db3a87e"}, + {file = "pymongo-3.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a1ba93be779a9b8e5e44f5c133dc1db4313661cead8a2fd27661e6cb8d942ee9"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4294f2c1cd069b793e31c2e6d7ac44b121cf7cedccd03ebcc30f3fc3417b314a"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:845b178bd127bb074835d2eac635b980c58ec5e700ebadc8355062df708d5a71"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:176fdca18391e1206c32fb1d8265628a84d28333c20ad19468d91e3e98312cd1"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:28bfd5244d32faf3e49b5a8d1fab0631e922c26e8add089312e4be19fb05af50"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:f38b35ecd2628bf0267761ed659e48af7e620a7fcccfccf5774e7308fb18325c"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:cebb3d8bcac4a6b48be65ebbc5c9881ed4a738e27bb96c86d9d7580a1fb09e05"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:80710d7591d579442c67a3bc7ae9dcba9ff95ea8414ac98001198d894fc4ff46"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d7baa847383b9814de640c6f1a8553d125ec65e2761ad146ea2e75a7ad197c"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:602284e652bb56ca8760f8e88a5280636c5b63d7946fca1c2fe0f83c37dffc64"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2d763d05ec7211313a06e8571236017d3e61d5fef97fcf34ec4b36c0b6556"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a6e4dccae8ef5dd76052647d78f02d5d0ffaff1856277d951666c54aeba3ad2"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1fc4d3985868860b6585376e511bb32403c5ffb58b0ed913496c27fd791deea"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4e5d163e6644c2bc84dd9f67bfa89288c23af26983d08fefcc2cbc22f6e57e6"}, + {file = "pymongo-3.12.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8d92c6bb9174d47c2257528f64645a00bbc6324a9ff45a626192797aff01dc14"}, + {file = "pymongo-3.12.3-cp38-cp38-win32.whl", hash = "sha256:b0db9a4691074c347f5d7ee830ab3529bc5ad860939de21c1f9c403daf1eda9a"}, + {file = "pymongo-3.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:d81047341ab56061aa4b6823c54d4632579c3b16e675089e8f520e9b918a133b"}, + {file = "pymongo-3.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07398d8a03545b98282f459f2603a6bb271f4448d484ed7f411121a519a7ea48"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b7df0d99e189b7027d417d4bfd9b8c53c9c7ed5a0a1495d26a6f547d820eca88"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a283425e6a474facd73072d8968812d1d9058490a5781e022ccf8895500b83ce"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:2577b8161eeae4dd376d13100b2137d883c10bb457dd08935f60c9f9d4b5c5f6"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:517b09b1dd842390a965a896d1327c55dfe78199c9f5840595d40facbcd81854"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:2567885ff0c8c7c0887ba6cefe4ae4af96364a66a7069f924ce0cd12eb971d04"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:71c5c200fd37a5322706080b09c3ec8907cf01c377a7187f354fc9e9e13abc73"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:14dee106a10b77224bba5efeeb6aee025aabe88eb87a2b850c46d3ee55bdab4a"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f340a2a908644ea6cccd399be0fb308c66e05d2800107345f9f0f0d59e1731c4"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b4c535f524c9d8c86c3afd71d199025daa070859a2bdaf94a298120b0de16db"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8455176fd1b86de97d859fed4ae0ef867bf998581f584c7a1a591246dfec330f"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf254a1a95e95fdf4eaa25faa1ea450a6533ed7a997f9f8e49ab971b61ea514d"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a3540e21213cb8ce232e68a7d0ee49cdd35194856c50b8bd87eeb572fadd42"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e7a5d0b9077e8c3e57727f797ee8adf12e1d5e7534642230d98980d160d1320"}, + {file = "pymongo-3.12.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0be605bfb8461384a4cb81e80f51eb5ca1b89851f2d0e69a75458c788a7263a4"}, + {file = "pymongo-3.12.3-cp39-cp39-win32.whl", hash = "sha256:2157d68f85c28688e8b723bbe70c8013e0aba5570e08c48b3562f74d33fc05c4"}, + {file = "pymongo-3.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfa217bf8cf3ff6b30c8e6a89014e0c0e7b50941af787b970060ae5ba04a4ce5"}, + {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"}, +] +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"}, +] +python-lsp-jsonrpc = [ + {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"}, + {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, +] +python-lsp-server = [ + {file = "python-lsp-server-1.3.3.tar.gz", hash = "sha256:1b48ccd8b70103522e8a8b9cb9ae1be2b27a5db0dfd661e7e44e6253ebefdc40"}, + {file = "python_lsp_server-1.3.3-py3-none-any.whl", hash = "sha256:ea7b1e623591ccbfbbf8e5dfe0df8119f27863125a58bdcacbacd1937d8e8cb3"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +requests-oauthlib = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] +requests-toolbelt = [ + {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, + {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, +] +rope = [ + {file = "rope-0.22.0-py3-none-any.whl", hash = "sha256:2847220bf72ead09b5abe72b1edc9cacff90ab93663ece06913fc97324167870"}, + {file = "rope-0.22.0.tar.gz", hash = "sha256:b00fbc064a26fc62d7220578a27fd639b2fad57213663cc396c137e92d73f10f"}, +] +smmap = [ + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, + {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, +] +tweepy = [ + {file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"}, + {file = "tweepy-4.5.0.tar.gz", hash = "sha256:12cc4b0a3d7b745928b08c3eb55a992236895e00028584d11fa41258f07df1b9"}, +] +ujson = [ + {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, + {file = "ujson-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0cae4a9c141856f7ad1a79c17ff1aaebf7fd8faa2f2c2614c37d6f82ed261d96"}, + {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba63b789d83ca92237dbc72041a268d91559f981c01763a107105878bae442e"}, + {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4e8f71e2fd42dce245bace7e2aa97dabef13926750a351eadca89a1e0f1abd"}, + {file = "ujson-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f73946c047a38640b1f5a2a459237b7bdc417ab028a76c796e4eea984b359b9"}, + {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afe91153c2046fa8210b92def513124e0ea5b87ad8fa4c14fef8197204b980f1"}, + {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b1ef400fc73ab0cb61b74a662ad4207917223aba6f933a9fea9b0fbe75de2361"}, + {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c8a884d60dd2eed2fc95a9474d57ead82adf254f54caffb3d9e8ed185c49aba"}, + {file = "ujson-5.1.0-cp310-cp310-win32.whl", hash = "sha256:173b90a2c2836ee42f708df88ecfe3efbc4d868df73c9fcea8cb8f6f3ab93892"}, + {file = "ujson-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c45ad95e82155372d9908774db46e0ef7880af28a734d0b14eaa4f505e64982"}, + {file = "ujson-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4155a7c29bf330329519027c815e15e381c1fff22f50d26f135584d482bbd95d"}, + {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa616d0d3c594785c6e9b7f42686bb1c86f9e64aa0f30a72c86d8eb315f54194"}, + {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a48efcb5d3695b295c26835ed81048da8cd40e76c4fde2940c807aa452b560c9"}, + {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838d35eb9006d36f9241e95958d9f4819bcf1ea2ec155daf92d5751c31bcc62b"}, + {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:05aa6c7297a22081f65497b6f586de6b7060ea47c3ecda80896f47200e9dbf04"}, + {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ce441ab7ad1db592e2db95b6c2a1eb882123532897340afac1342c28819e9833"}, + {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9937e819196b894ffd00801b24f1042dabda142f355313c3f20410993219bc4f"}, + {file = "ujson-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:06bed66ae62d517f67a61cf53c056800b35ef364270723168a1db62702e2d30c"}, + {file = "ujson-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:74e41a0222e6e8136e38f103d6cc228e4e20f1c35cc80224a42804fd67fb35c8"}, + {file = "ujson-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7bbb87f040e618bebe8c6257b3e4e8ae2f708dcbff3270c84718b3360a152799"}, + {file = "ujson-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:68e38122115a8097fbe1cfe52979a797eaff91c10c1bf4b27774e5f30e7f723a"}, + {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b09843123425337d2efee5c8ff6519e4dfc7b044db66c8bd560517fc1070a157"}, + {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dca10174a3bd482d969a2d12d0aec2fdd63fb974e255ec0147e36a516a2d68a"}, + {file = "ujson-5.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202ae52f4a53f03c42ead6d046b1a146517e93bd757f517bdeef0a26228e0260"}, + {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7a4bed7bd7b288cf73ba47bda27fdd1d78ef6906831489e7f296aef9e786eccb"}, + {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d423956f8dfd98a075c9338b886414b6e3c2817dbf67935797466c998af39936"}, + {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:083c1078e4de3a39019e590c43865b17e07a763fee25b012e650bb4f42c89703"}, + {file = "ujson-5.1.0-cp38-cp38-win32.whl", hash = "sha256:31671ad99f0395eb881d698f2871dc64ff00fbd4380c5d9bfd8bff3d4c8f8d88"}, + {file = "ujson-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:994eaf4369e6bc24258f59fe8c6345037abcf24557571814e27879851c4353aa"}, + {file = "ujson-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00d6ea9702c2eaeaf1a826934eaba1b4c609c873379bf54e36ba7b7e128edf94"}, + {file = "ujson-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a53c4fe8e1c067e6c98b4526e982ed9486f08578ad8eb5f0e94f8cadf0c1d911"}, + {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:368f855779fded560724a6448838304621f498113a116d66bc5ed5ad5ad3ca92"}, + {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd97e45a0f450ba2c43cda18147e54b8e41e886c22e3506c62f7d61e9e53b0d"}, + {file = "ujson-5.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caeadbf95ce277f1f8f4f71913bc20c01f49fc9228f238920f9ff6f7645d2a5f"}, + {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:681fed63c948f757466eeb3aea98873e2ab8b2b18e9020c96a97479a513e2018"}, + {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6fc4376266ae67f6d8f9e69386ab950eb84ba345c6fdbeb1884fa5b773c8c76b"}, + {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:585271d6ad545a2ccfc237582f70c160e627735c89d0ca2bde24afa321bc0750"}, + {file = "ujson-5.1.0-cp39-cp39-win32.whl", hash = "sha256:b631af423e6d5d35f9f37fbcc4fbdb6085abc1c441cf864c64b7fbb5b150faf7"}, + {file = "ujson-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:08265db5ccff8b521ff68aee13a417d68cca784d7e711d961b92fda6ccffcc4f"}, + {file = "ujson-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2b1c372583eb4363b42e21222d3a18116a41973781d502d61e1b0daf4b8352f"}, + {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51142c9d40439f299594e399bef8892a16586ded54c88d3af926865ca221a177"}, + {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba8be1717b1867a85b2413a8585bad0e4507a22d6af2c244e1c74151f6d5cc0"}, + {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b26d9d6eb9a0979d37f28c715e717a409c9e03163e5cd8fa73aab806351ab5"}, + {file = "ujson-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b2c7e4afde0d36926b091fa9613b18b65e911fcaa60024e8721f2dcfedc25329"}, + {file = "ujson-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:110633a8dda6c8ca78090292231e15381f8b2423e998399d4bc5f135149c722b"}, + {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdac161127ef8e0889180a4c07475457c55fe0bbd644436d8f4c7ef07565d653"}, + {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:452990c2b18445a7379a45873527d2ec47789b9289c13a17a3c1cc76b9641126"}, + {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5304ad25d100d50b5bc8513ef110335df678f66c7ccf3d4728c0c3aa69e08e0c"}, + {file = "ujson-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ce620a6563b21aa3fbb1658bc1bfddb484a6dad542de1efb5121eb7bb4f2b93a"}, + {file = "ujson-5.1.0.tar.gz", hash = "sha256:a88944d2f99db71a3ca0c63d81f37e55b660edde0b07216fb65a3e46403ef004"}, +] +ulid-py = [ + {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, + {file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"}, +] +urllib3 = [ + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, +] +wrapt = [ + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, +] +yapf = [ + {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"}, + {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"}, +] +yarl = [ + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"}, + {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"}, + {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"}, + {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"}, + {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"}, + {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"}, + {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"}, + {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"}, + {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"}, + {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"}, + {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"}, + {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"}, + {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"}, + {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7eee61c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "jarvis" +version = "2.0.0a0" +description = "J.A.R.V.I.S. admin bot" +authors = ["Zevaryx "] + +[tool.poetry.dependencies] +python = "^3.10" +PyYAML = "^6.0" +dis-snek = "^5.0.0" +GitPython = "^3.1.26" +mongoengine = "^0.23.1" +opencv-python = "^4.5.5" +Pillow = "^9.0.0" +psutil = "^5.9.0" +python-gitlab = "^3.1.1" +ulid-py = "^1.1.0" +tweepy = "^4.5.0" +orjson = "^3.6.6" + +[tool.poetry.dev-dependencies] +python-lsp-server = {extras = ["all"], version = "^1.3.3"} +black = "^22.1.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/run.py b/run.py index 844831e..7e6a73e 100755 --- a/run.py +++ b/run.py @@ -1,117 +1,5 @@ -#!/bin/python3 -# flake8: noqa -from importlib import reload as ireload -from multiprocessing import Process, Value, freeze_support -from pathlib import Path -from time import sleep - -import git - -import jarvis -from jarvis.config import get_config - - -def run(): - ctx = None - while True: - ireload(jarvis) - ctx = jarvis.run(ctx) - - -def restart(): - global jarvis_process - Path(get_pid_file()).unlink() - jarvis_process.kill() - jarvis_process = Process(target=run, name="jarvis") - jarvis_process.start() - - -def update(): - repo = git.Repo(".") - dirty = repo.is_dirty() - if dirty: - print(" Local system has uncommitted changes.") - current_hash = repo.head.object.hexsha - origin = repo.remotes.origin - origin.fetch() - if current_hash != origin.refs["main"].object.hexsha: - if dirty: - return 2 - origin.pull() - return 0 - return 1 - - -def get_pid_file(): - return f"jarvis.{get_pid()}.pid" - - -def get_pid(): - global jarvis_process - return jarvis_process.pid - - -def cli(): - pfile = Path(get_pid_file()) - while not pfile.exists(): - sleep(0.2) - print( - """ - All systems online. - -Command List: - (R)eload - (U)pdate - (Q)uit - """ - ) - while True: - cmd = input("> ") - if cmd.lower() in ["q", "quit", "e", "exit"]: - print(" Shutting down core systems...") - pfile.unlink() - break - if cmd.lower() in ["u", "update"]: - print(" Updating core systems...") - status = update() - if status == 0: - restart() - pfile = Path(get_pid_file()) - while not pfile.exists(): - sleep(0.2) - print(" Core systems successfully updated.") - elif status == 1: - print(" No core updates available.") - elif status == 2: - print(" Core system update available, but core is dirty.") - if cmd.lower() in ["r", "reload"]: - print(" Reloading core systems...") - restart() - pfile = Path(get_pid_file()) - while not pfile.exists(): - sleep(0.2) - print(" All systems reloaded.") - +"""Main run file for J.A.R.V.I.S.""" +from jarvis import run if __name__ == "__main__": - freeze_support() - config = get_config() - pid_file = Value("i", 0) - jarvis_process = Process(target=run, name="jarvis") - logo = jarvis.logo.get_logo(config.logo) - print(logo) - print("Initializing....") - print(" Updating core systems...") - status = update() - if status == 0: - print(" Core systems successfully updated") - elif status == 1: - print(" No core updates available.") - elif status == 2: - print(" Core updates available, but not applied.") - print(" Starting core systems...") - jarvis_process.start() - cli() - if jarvis_process.is_alive(): - jarvis_process.kill() - print("All systems shut down.") + run()