From 90558979658700132d3e850cdcfc43172bc866fe Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 1 Feb 2022 16:29:11 -0700 Subject: [PATCH 001/365] Remove threading --- run.py | 118 ++------------------------------------------------------- 1 file changed, 3 insertions(+), 115 deletions(-) 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() From 9fa3e2c26b03054505d4945b94ce641ee056df5a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 1 Feb 2022 17:54:13 -0700 Subject: [PATCH 002/365] Migrate permission checks, utils, and init --- jarvis/__init__.py | 48 +- jarvis/cogs/admin/__init__.py | 4 +- jarvis/config.py | 4 + jarvis/utils/__init__.py | 58 +- jarvis/utils/cachecog.py | 17 +- jarvis/utils/field.py | 16 - jarvis/utils/permissions.py | 15 +- poetry.lock | 1476 +++++++++++++++++++++++++++++++++ pyproject.toml | 25 + 9 files changed, 1565 insertions(+), 98 deletions(-) delete mode 100644 jarvis/utils/field.py create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 15b64f1..e4b2a27 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,15 +1,8 @@ """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 from mongoengine import connect -from psutil import Process from jarvis import logo # noqa: F401 from jarvis import tasks, utils @@ -24,53 +17,26 @@ 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.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=utils.get_prefix, sync_interactions=jconfig.sync) -slash = SlashCommand(jarvis, sync_commands=False, sync_on_cog_reload=True) -jarvis_self = Process() -__version__ = "1.11.2" +__version__ = "2.0.0a0" -@jarvis.event +@jarvis.add_listener 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 -def run(ctx: dict = None) -> Optional[dict]: +def run() -> None: """Run J.A.R.V.I.S.""" - global restart_ctx - if ctx: - restart_ctx = ctx connect( db="ctc2", alias="ctc2", @@ -84,8 +50,10 @@ 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( " https://discord.com/api/oauth2/authorize?client_id=" + "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) # noqa: W503 diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index 0c76e20..c3fad57 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,10 +1,10 @@ """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 -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)) diff --git a/jarvis/config.py b/jarvis/config.py index 0338b29..21b56a7 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, 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("SYNC_COMMANDS", False) self.__db_loaded = False self.__mongo = MongoClient(**self.mongo["connect"]) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 2887a0d..d93ba9b 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,9 +1,11 @@ """J.A.R.V.I.S. Utility Functions.""" from datetime import datetime from pkgutil import iter_modules +from typing import Any, Callable, Iterable, Optional, TypeVar import git -from discord import Color, Embed, Message +from dis_snek.models.discord.embed import Color, Embed +from dis_snek.models.discord.message import Message from discord.ext import commands import jarvis.cogs @@ -12,6 +14,30 @@ 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.utcnow() + embed = Embed( + title=title, + description=description, + color=parse_color_hex(color), + timestamp=timestamp, + fields=fields, + **kwargs, + ) + return embed + def convert_bytesize(b: int) -> str: """Convert bytes amount to human readable.""" @@ -57,29 +83,6 @@ def parse_color_hex(hex: str) -> Color: 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 +102,10 @@ 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[[T], Any], seq: Iterable[T]) -> Optional[T]: + for element in seq: + if predicate(element): + return element + return None diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py index 12c043d..89be58f 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, Snek +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: Snek): 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..7977441 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 Context, Permissions from jarvis.config import get_config @@ -7,22 +7,21 @@ 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: + def predicate(ctx: Context) -> 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: Context) -> bool: """Extended check predicate.""" # noqa: D401 - return await commands.has_permissions(administrator=True).predicate(ctx) or await original(ctx) + return ctx.author.has_permission(Permissions.ADMINISTRATOR) or ctx.author.has_permission(*perms) - return commands.check(extended_check) + return predicate diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..cc52890 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1476 @@ +[[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 = "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 = "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-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 = "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 = "86fcc444c9ed72b727e6f3fed10dbd26bcd0654a7cf41b0f7286e3f8e207665b" + +[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"}, +] +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"}, +] +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-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"}, +] +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..45956c2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[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" + +[tool.poetry.dev-dependencies] +python-lsp-server = {extras = ["all"], version = "^1.3.3"} +black = "^22.1.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From e7227540d4e234214f1125f86234bb8cf61a2dc5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 1 Feb 2022 17:54:46 -0700 Subject: [PATCH 003/365] Migrate events --- jarvis/events/guild.py | 8 ++++---- jarvis/events/member.py | 6 +++--- jarvis/events/message.py | 17 ++++++++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/jarvis/events/guild.py b/jarvis/events/guild.py index 5f7dc62..119b815 100644 --- a/jarvis/events/guild.py +++ b/jarvis/events/guild.py @@ -1,17 +1,17 @@ """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 dis_snek import Snake +from dis_snek.models.discord.guild import Guild from jarvis.db.models import Setting +from jarvis.utils import find class GuildEventHandler(object): """J.A.R.V.I.S. guild event handler.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snake): self.bot = bot self.bot.add_listener(self.on_guild_join) diff --git a/jarvis/events/member.py b/jarvis/events/member.py index 8dec15e..f849a9c 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 +from dis_snek.models.discord.user import Member from jarvis.db.models import Mute, Setting @@ -8,7 +8,7 @@ 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) diff --git a/jarvis/events/message.py b/jarvis/events/message.py index 2cfea81..52a1211 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -1,13 +1,13 @@ """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 +from dis_snek.models.discord.channel import DMChannel +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 import build_embed, find from jarvis.utils.field import Field invites = re.compile( @@ -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) @@ -208,3 +208,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) From 7e4823a73129289118dd1363f26c982986dd39bb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 1 Feb 2022 17:55:14 -0700 Subject: [PATCH 004/365] Migrate admin/ban cog --- jarvis/cogs/admin/ban.py | 212 +++++++++++++++------------------------ 1 file changed, 79 insertions(+), 133 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 646049d..b04e7c9 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -2,30 +2,32 @@ 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 jarvis.db.models import Ban, Unban -from jarvis.utils import build_embed +from jarvis.utils import build_embed, find 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 +39,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 +56,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,55 +79,34 @@ 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", option_type=OptionTypes.USER, required=True) + @slash_option(name="reason", description="Ban reason", option_type=OptionTypes.STRING, required=True) + @slash_option( + name="btype", + description="Ban type", + option_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) + @admin_or_permissions(Permissions.BAN_MEMBERS) async def _ban( self, - ctx: SlashContext, + ctx: InteractionContext, user: User = None, reason: str = None, btype: str = "perm", @@ -160,10 +141,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}", @@ -173,7 +154,7 @@ class BanCog(CacheCog): user_embed.set_author( name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.avatar, ) user_embed.set_thumbnail(url=ctx.guild.icon_url) @@ -190,7 +171,7 @@ class BanCog(CacheCog): 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,28 +180,13 @@ 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, - ), - ], - ) - @admin_or_permissions(ban_members=True) + @slash_command(name="unban", description="Unban a user") + @slash_option(name="user", description="User to unban", option_type=OptionTypes.STRING, required=True) + @slash_option(name="reason", description="Unban reason", option_type=OptionTypes.STRING, required=True) + @admin_or_permissions(Permissions.BAN_MEMBERS) async def _unban( self, - ctx: SlashContext, + ctx: InteractionContext, user: str, reason: str, ) -> None: @@ -236,24 +202,24 @@ 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 = [x for x in filter(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```" await ctx.send(message) @@ -299,37 +265,28 @@ class BanCog(CacheCog): ).save() 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", + option_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", + option_type=OptionTypes.INTEGER, + required=False, + choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], + ) + @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: @@ -351,9 +308,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 +327,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" @@ -403,18 +360,7 @@ class BanCog(CacheCog): 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 +372,4 @@ class BanCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) From 12870bba17d27d92133a25f6aa9479a849d2d90a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 18:34:04 -0700 Subject: [PATCH 005/365] Migrate admin/kick, ref #91 --- jarvis/cogs/admin/kick.py | 44 +++++++++++++-------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 9c74910..28d2d1d 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,42 +1,30 @@ """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, Snek +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 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): """J.A.R.V.I.S. KickCog.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Snek): 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", option_type=OptionTypes.USER, required=True) + @slash_option(name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True) @admin_or_permissions(kick_members=True) - async def _kick(self, ctx: SlashContext, user: User, reason: str = None) -> None: + 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) return @@ -46,8 +34,6 @@ class KickCog(CacheCog): if len(reason) > 100: await ctx.send("Reason must be < 100 characters", hidden=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,7 +42,7 @@ class KickCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, + name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.avatar_url, ) embed.set_thumbnail(url=ctx.guild.icon_url) @@ -68,7 +54,7 @@ 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}", From e9532d1235c70529ffe9a436d41a655900864873 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:44:27 -0700 Subject: [PATCH 006/365] Add jarvis.utils.get --- jarvis/utils/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index d93ba9b..1bd4fbe 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Utility Functions.""" from datetime import datetime +from operator import attrgetter from pkgutil import iter_modules from typing import Any, Callable, Iterable, Optional, TypeVar @@ -109,3 +110,20 @@ def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: if predicate(element): return element return None + + +def get(iterable: Iterable[T], **attrs: Any) -> Optional[T]: + if len(attrs) == 1: + k, v = attrs.popitem() + pred = attrgetter(k.replace("__", ".")) + for elem in iterable: + if pred(elem) == v: + return elem + return None + + converted = [(attrgetter(attr.replace("__", ".")), value) for attr, value in attrs.items()] + + for elem in iterable: + if all(pred(elem) == value for pred, value in converted): + return elem + return None From cd49bd9e78c68bf2121bfb932f5b927311c30c6a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:44:53 -0700 Subject: [PATCH 007/365] Fix permissions check to use any instead of all permissions --- jarvis/utils/permissions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index 7977441..5a401eb 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -22,6 +22,8 @@ def admin_or_permissions(*perms: list) -> bool: async def predicate(ctx: Context) -> bool: """Extended check predicate.""" # noqa: D401 - return ctx.author.has_permission(Permissions.ADMINISTRATOR) or ctx.author.has_permission(*perms) + 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 predicate From a10d297e4eeafc1b7bb7e8eb2e002a21f08ad935 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:45:55 -0700 Subject: [PATCH 008/365] Change from CacheCog to Scale, fix permissions and formatting --- jarvis/cogs/admin/kick.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 28d2d1d..34f5ed2 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. KickCog.""" -from dis_snek import InteractionContext, Snek +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 ( @@ -10,20 +10,16 @@ from dis_snek.models.snek.application_commands import ( from jarvis.db.models import Kick from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog 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: Snek): - super().__init__(bot) - @slash_command(name="kick", description="Kick a user") @slash_option(name="user", description="User to kick", option_type=OptionTypes.USER, required=True) @slash_option(name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True) - @admin_or_permissions(kick_members=True) + @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) From 97c83aa370d531e78f4e5e0aede4b73f839e1b27 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:46:34 -0700 Subject: [PATCH 009/365] Migrate admin/mute, change to use built-in timeout functionality, ref #91 --- jarvis/cogs/admin/mute.py | 146 +++++++++++++++----------------------- 1 file changed, 59 insertions(+), 87 deletions(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 7bf851c..9261b18 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,48 +1,56 @@ """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 dis_snek import InteractionContext, Permissions, Scale, Snek +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 jarvis.db.models import Mute, Setting 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: Snek): 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", option_type=OptionTypes.USER, required=True) + @slash_option( + name="reason", + description="Reason for mute", + option_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="time", + description="Duration of mute, default 1", + option_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="scale", + description="Time scale, default Hour(s)", + option_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: + @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) return @@ -52,81 +60,45 @@ class MuteCog(commands.Cog): if len(reason) > 100: await ctx.send("Reason must be < 100 characters", hidden=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)", hidden=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 - _ = Mute( - user=user.id, - reason=reason, - admin=ctx.author.id, - guild=ctx.guild.id, - duration=duration, - active=True if duration >= 0 else False, - ).save() + + await user.timeout(communication_disabled_until=duration, reason=reason) 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, + name=user.display_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_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, - ) - ], - ) - @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, - ) + @slash_command(name="unmute", description="Unmute a user") + @slash_option(name="user", description="User to unmute", option_type=OptionTypes.USER, required=True) + @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(): + await ctx.send("User is not muted", hidden=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, + name=user.display_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_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) From ab0e24129d411576d305688f3b2e8b30e580a1a6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:54:09 -0700 Subject: [PATCH 010/365] Remove unmute task --- jarvis/tasks/__init__.py | 3 +-- jarvis/tasks/unmute.py | 27 --------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 jarvis/tasks/unmute.py diff --git a/jarvis/tasks/__init__.py b/jarvis/tasks/__init__.py index 3da9656..e5cd73c 100644 --- a/jarvis/tasks/__init__.py +++ b/jarvis/tasks/__init__.py @@ -1,10 +1,9 @@ """J.A.R.V.I.S. background task handlers.""" -from jarvis.tasks import unban, unlock, unmute, unwarn +from jarvis.tasks import unban, unlock, unwarn def init() -> None: """Start the background task handlers.""" unban.unban.start() unlock.unlock.start() - unmute.unmute.start() unwarn.unwarn.start() 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 From 446e78b6aef5caf37fffe20a95f41895db6d6d28 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 19:55:17 -0700 Subject: [PATCH 011/365] Reduce max line length from 120 to 100 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e102ae9..f6839aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: rev: 21.7b0 hooks: - id: black - args: [--line-length=120] + args: [--line-length=100] - repo: https://github.com/pre-commit/mirrors-isort rev: V5.9.3 From 28a1cb0dc53f54e0a66cd7a924b40e0824f6fd32 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:00:47 -0700 Subject: [PATCH 012/365] Update pre-commit --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6839aa..b58f649 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ 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 @@ -16,23 +16,23 @@ 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=100] - 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-annotations~=2.7 - flake8-bandit~=2.1 - - flake8-docstrings~=1.5 - args: [--max-line-length=120, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204] + - flake8-docstrings~=1.6 + args: [--max-line-length=100, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204] From 6d40e5acc518977bb3e43c381a3e89125fe4459d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:09:11 -0700 Subject: [PATCH 013/365] Ignore E203 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b58f649..62194d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,4 +35,4 @@ repos: - flake8-annotations~=2.7 - flake8-bandit~=2.1 - flake8-docstrings~=1.6 - args: [--max-line-length=100, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204] + args: [--max-line-length=100, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204 E203] From 7ad353e62e74f9f059cd91095d95a004b60397af Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:22:51 -0700 Subject: [PATCH 014/365] Update black flags --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62194d2..7034078 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: rev: 22.1.0 hooks: - id: black - args: [--line-length=100] + args: [--line-length=100, --fast, --target-version=py310] - repo: https://github.com/pre-commit/mirrors-isort rev: V5.10.1 From d7c9742ce5db318022671cf8e89c73a792430b6d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:25:04 -0700 Subject: [PATCH 015/365] Remove 90% of owner cog --- jarvis/cogs/owner.py | 197 +------------------------------------------ 1 file changed, 1 insertion(+), 196 deletions(-) diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py index 7979ad0..a19dcd9 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -1,20 +1,9 @@ """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 - -import discord -from discord import DMChannel, User +from discord import 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): @@ -28,127 +17,6 @@ class OwnerCog(commands.Cog): 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: @@ -177,69 +45,6 @@ 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: """Add OwnerCog to J.A.R.V.I.S.""" From 7a174105149b5f2c66f69b3d9b089e943e51992f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:28:40 -0700 Subject: [PATCH 016/365] The 100-character line reckoning. Reformatted every file that needed it --- jarvis/__init__.py | 4 +-- jarvis/cogs/admin/ban.py | 35 ++++++++++++++----- jarvis/cogs/admin/kick.py | 8 +++-- jarvis/cogs/admin/mute.py | 21 ++++++++--- jarvis/cogs/admin/purge.py | 4 ++- jarvis/cogs/admin/roleping.py | 11 ++++-- jarvis/cogs/admin/warning.py | 16 ++++++--- jarvis/cogs/autoreact.py | 8 +++-- jarvis/cogs/ctc2.py | 14 +++++--- jarvis/cogs/dbrand.py | 3 +- jarvis/cogs/dev.py | 14 +++++--- jarvis/cogs/error.py | 10 ++++-- jarvis/cogs/gitlab.py | 50 +++++++++++++++++++------- jarvis/cogs/modlog/command.py | 4 ++- jarvis/cogs/modlog/member.py | 4 ++- jarvis/cogs/modlog/message.py | 8 +++-- jarvis/cogs/starboard.py | 9 +++-- jarvis/cogs/twitter.py | 66 +++++++++++++++++++++++++++-------- jarvis/cogs/util.py | 8 +++-- jarvis/events/message.py | 19 ++++++---- jarvis/tasks/unban.py | 4 ++- jarvis/utils/__init__.py | 2 +- 22 files changed, 236 insertions(+), 86 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index e4b2a27..47d572b 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -4,7 +4,7 @@ import logging from dis_snek import Intents, Snake from mongoengine import connect -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 @@ -56,7 +56,7 @@ def run() -> None: print( " 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 diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index b04e7c9..7b197f5 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -90,8 +90,12 @@ class BanCog(CacheCog): await ctx.send(embed=embed) @slash_command(name="ban", description="Ban a user") - @slash_option(name="user", description="User to ban", option_type=OptionTypes.USER, required=True) - @slash_option(name="reason", description="Ban reason", option_type=OptionTypes.STRING, required=True) + @slash_option( + name="user", description="User to ban", option_type=OptionTypes.USER, required=True + ) + @slash_option( + name="reason", description="Ban reason", option_type=OptionTypes.STRING, required=True + ) @slash_option( name="btype", description="Ban type", @@ -181,8 +185,12 @@ class BanCog(CacheCog): await self.discord_apply_ban(ctx, reason, user, duration, active, fields, mtype) @slash_command(name="unban", description="Unban a user") - @slash_option(name="user", description="User to unban", option_type=OptionTypes.STRING, required=True) - @slash_option(name="reason", description="Unban reason", option_type=OptionTypes.STRING, required=True) + @slash_option( + name="user", description="User to unban", option_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="reason", description="Unban reason", option_type=OptionTypes.STRING, required=True + ) @admin_or_permissions(Permissions.BAN_MEMBERS) async def _unban( self, @@ -219,9 +227,14 @@ class BanCog(CacheCog): if len(results) > 1: active_bans = [] for ban in bans: - active_bans.append("{0} ({1}): {2}".format(ban.user.username, 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: @@ -263,9 +276,13 @@ class BanCog(CacheCog): admin=ctx.author.id, reason=reason, ).save() - await ctx.send("Unable to find user in Discord, " + "but removed entry from database.") + await ctx.send( + "Unable to find user in Discord, " + "but removed entry from database." + ) - @slash_command(name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans") + @slash_command( + name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans" + ) @slash_option( name="btype", description="Ban type", @@ -356,7 +373,7 @@ class BanCog(CacheCog): 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 = build_embed(title=title, description="", fields=fields[i : i + 5]) embed.set_thumbnail(url=ctx.guild.icon_url) pages.append(embed) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 34f5ed2..b551225 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -17,8 +17,12 @@ class KickCog(Scale): """J.A.R.V.I.S. KickCog.""" @slash_command(name="kick", description="Kick a user") - @slash_option(name="user", description="User to kick", option_type=OptionTypes.USER, required=True) - @slash_option(name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True) + @slash_option( + name="user", description="User to kick", option_type=OptionTypes.USER, required=True + ) + @slash_option( + name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True + ) @admin_or_permissions(Permissions.BAN_MEMBERS) async def _kick(self, ctx: InteractionContext, user: User, reason: str) -> None: if not user or user == ctx.author: diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 9261b18..ae1e0f8 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -22,7 +22,9 @@ class MuteCog(Scale): self.bot = bot @slash_command(name="mute", description="Mute a user") - @slash_option(name="user", description="User to mute", option_type=OptionTypes.USER, required=True) + @slash_option( + name="user", description="User to mute", option_type=OptionTypes.USER, required=True + ) @slash_option( name="reason", description="Reason for mute", @@ -47,7 +49,9 @@ class MuteCog(Scale): SlashCommandChoice(name="Week(s)", value=604800), ], ) - @admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS) + @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: @@ -83,10 +87,17 @@ class MuteCog(Scale): await ctx.send(embed=embed) @slash_command(name="unmute", description="Unmute a user") - @slash_option(name="user", description="User to unmute", option_type=OptionTypes.USER, required=True) - @admin_or_permissions(Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS) + @slash_option( + name="user", description="User to unmute", option_type=OptionTypes.USER, required=True + ) + @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(): + if ( + not user.communication_disabled_until + or user.communication_disabled_until < datetime.now() # noqa: W503 + ): await ctx.send("User is not muted", hidden=True) return diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index adae1ac..6e95950 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -64,7 +64,9 @@ class PurgeCog(commands.Cog): ], ) @admin_or_permissions(manage_messages=True) - async def _autopurge_add(self, ctx: SlashContext, channel: TextChannel, delay: int = 30) -> None: + 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) return diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 46ee2c5..fe81488 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -97,7 +97,10 @@ class RolepingCog(CacheCog): role = 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 = [ + 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( @@ -242,7 +245,8 @@ class RolepingCog(CacheCog): if len(roleping.bypass["roles"]) == 10: await ctx.send( - "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass", + "Already have 10 roles in bypass. " + "Please consider consolidating roles for roleping bypass", hidden=True, ) return @@ -323,7 +327,8 @@ class RolepingCog(CacheCog): if len(roleping.bypass["roles"]) == 10: await ctx.send( - "Already have 10 roles in bypass. Please consider consolidating roles for roleping bypass", + "Already have 10 roles in bypass. " + "Please consider consolidating roles for roleping bypass", hidden=True, ) return diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index ec18e9a..81ed296 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -116,7 +116,9 @@ class WarningCog(CacheCog): 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: @@ -146,8 +148,10 @@ 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, @@ -171,8 +175,10 @@ 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, diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 378f705..6d1263f 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -108,7 +108,9 @@ class AutoReactCog(commands.Cog): 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}") + await ctx.send( + f"Please create autoreact first with /autoreact create {channel.mention}" + ) return if emote in exists.reactions: await ctx.send( @@ -188,7 +190,9 @@ class AutoReactCog(commands.Cog): 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) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index b236e69..381686e 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -18,7 +18,7 @@ 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, ) @@ -54,13 +54,19 @@ class CTCCog(CacheCog): async def _pw(self, ctx: SlashContext, guess: str) -> None: if len(guess) > 800: await ctx.send( - "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses < 800 characters.", + ( + "Listen here, dipshit. Don't be like <@256110768724901889>. " + "Make your guesses < 800 characters." + ), hidden=True, ) return elif not valid.fullmatch(guess): await ctx.send( - "Listen here, dipshit. Don't be like <@256110768724901889>. Make your guesses *readable*.", + ( + "Listen here, dipshit. Don't be like <@256110768724901889>. " + "Make your guesses *readable*." + ), hidden=True, ) return @@ -124,7 +130,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") diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 79e4904..884de2b 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -230,7 +230,8 @@ class DbrandCog(commands.Cog): embed = build_embed( title="Check Shipping Times", description=( - "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)" + "Country not found.\nYou can [view all shipping " + "destinations here](https://dbrand.com/shipping)" ), fields=[], url="https://dbrand.com/shipping", diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 4345c0c..7df8699 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -18,9 +18,11 @@ 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 +31,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,7 +49,7 @@ 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() @@ -253,7 +255,9 @@ class DevCog(commands.Cog): ) @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 + output = subprocess.check_output( # noqa: S603, S607 + ["tokei", "-C", "--sort", "code"] + ).decode("UTF-8") await ctx.send(f"```\n{output}\n```") diff --git a/jarvis/cogs/error.py b/jarvis/cogs/error.py index 82f93a9..3715d03 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,13 +30,16 @@ 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): + 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) 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", + "Command on cooldown. " + f"Please wait {error.retry_after:0.2f}s before trying again", hidden=True, ) else: diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index a3f2a95..75f0e0f 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -46,7 +46,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: @@ -62,7 +64,9 @@ class GitlabCog(CacheCog): color = self.project.labels.get(issue.labels[0]).color fields.append(Field(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") + 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)) if issue.milestone: fields.append( @@ -86,7 +90,9 @@ class GitlabCog(CacheCog): icon_url=issue.author["avatar_url"], 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( @@ -110,7 +116,9 @@ class GitlabCog(CacheCog): await ctx.send("Milestone does not exist.", hidden=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( @@ -143,7 +151,9 @@ 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( @@ -172,7 +182,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: @@ -191,10 +203,14 @@ class GitlabCog(CacheCog): color = "#00FFEE" fields.append(Field(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") + 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)) 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") + 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)) if mr.milestone: fields.append( @@ -218,7 +234,9 @@ class GitlabCog(CacheCog): icon_url=mr.author["avatar_url"], 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: @@ -248,7 +266,9 @@ 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( @@ -311,7 +331,7 @@ 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, @@ -398,7 +418,9 @@ 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, @@ -463,7 +485,9 @@ 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, diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index e5ccd4b..b03666d 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -45,5 +45,7 @@ class ModlogCommandCog(commands.Cog): name=ctx.author.name, icon_url=ctx.author.avatar_url, ) - embed.set_footer(text=f"{ctx.author.name}#{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..d6423cc 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( diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py index 8c779a6..1834877 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -47,7 +47,9 @@ class ModlogMessageCog(commands.Cog): icon_url=before.author.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() @@ -100,5 +102,7 @@ class ModlogMessageCog(commands.Cog): icon_url=message.author.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/starboard.py b/jarvis/cogs/starboard.py index df5d1d6..d2eb830 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -129,7 +129,9 @@ class StarboardCog(commands.Cog): ), create_option( name="channel", - description="Channel that has the message, required if different than command message", + description=( + "Channel that has the message, " "required if different than command message" + ), option_type=7, required=False, ), @@ -154,7 +156,9 @@ class StarboardCog(commands.Cog): 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 = [ + create_select_option(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list) + ] select = create_select( options=select_channels, @@ -303,3 +307,4 @@ class StarboardCog(commands.Cog): def setup(bot: commands.Bot) -> None: """Add StarboardCog to J.A.R.V.I.S.""" bot.add_cog(StarboardCog(bot)) + bot.add_cog(StarboardCog(bot)) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index c007585..65fe265 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -31,7 +31,9 @@ class TwitterCog(commands.Cog): def __init__(self, bot: commands.Bot): 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 = {} @@ -49,7 +51,9 @@ class TwitterCog(commands.Cog): 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])) + 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: @@ -57,7 +61,9 @@ class TwitterCog(commands.Cog): 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) + 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__ @@ -66,7 +72,9 @@ class TwitterCog(commands.Cog): 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}") + 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() @@ -79,7 +87,12 @@ class TwitterCog(commands.Cog): name="follow", description="Follow a Twitter account", options=[ - create_option(name="handle", description="Twitter account", option_type=COptionType.STRING, required=True), + create_option( + name="handle", + description="Twitter account", + option_type=COptionType.STRING, + required=True, + ), create_option( name="channel", description="Channel to post tweets into", @@ -91,7 +104,10 @@ class TwitterCog(commands.Cog): description="Mirror re-tweets?", option_type=COptionType.STRING, required=False, - choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")], + choices=[ + create_choice(name="Yes", value="Yes"), + create_choice(name="No", value="No"), + ], ), ], ) @@ -111,7 +127,9 @@ class TwitterCog(commands.Cog): try: latest_tweet = self.api.user_timeline(screen_name=handle, count=1)[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?", hidden=True + ) return count = Twitter.objects(guild=ctx.guild.id).count() @@ -155,7 +173,9 @@ class TwitterCog(commands.Cog): option = create_select_option(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 = create_select( + options=options, custom_id="to_delete", min_values=1, max_values=len(twitters) + ) components = [create_actionrow(select)] block = "\n".join(x.handle for x in twitters) @@ -167,7 +187,10 @@ 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 + self.bot, + check=lambda x: ctx.author.id == x.author.id, + messages=message, + timeout=60 * 5, ) for to_delete in context.selected_options: _ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete() @@ -175,7 +198,9 @@ class TwitterCog(commands.Cog): 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) + await 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"]: @@ -192,7 +217,10 @@ class TwitterCog(commands.Cog): description="Mirror re-tweets?", option_type=COptionType.STRING, required=True, - choices=[create_choice(name="Yes", value="Yes"), create_choice(name="No", value="No")], + choices=[ + create_choice(name="Yes", value="Yes"), + create_choice(name="No", value="No"), + ], ), ], ) @@ -209,7 +237,9 @@ class TwitterCog(commands.Cog): option = create_select_option(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 = create_select( + options=options, custom_id="to_update", min_values=1, max_values=len(twitters) + ) components = [create_actionrow(select)] block = "\n".join(x.handle for x in twitters) @@ -221,7 +251,10 @@ 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 + self.bot, + 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: @@ -233,8 +266,11 @@ class TwitterCog(commands.Cog): 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```", + content=( + f"{'Unfollowed' if not retweets else 'Followed'} " + "retweets from the following:" + f"\n```\n{block}\n```" + ), components=components, ) except asyncio.TimeoutError: diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 900c8b9..a1fec76 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -228,7 +228,9 @@ class UtilCog(commands.Cog): async def _server_info(self, ctx: SlashContext) -> None: guild: Guild = ctx.guild - owner = f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||" + owner = ( + f"{guild.owner.name}#{guild.owner.discriminator}" if guild.owner else "||`[redacted]`||" + ) region = guild.region categories = len(guild.categories) @@ -308,7 +310,9 @@ class UtilCog(commands.Cog): @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)], + options=[ + create_option(name="text", description="Text to encode", option_type=3, required=True) + ], ) async def _pigpen(self, ctx: SlashContext, text: str) -> None: outp = "`" diff --git a/jarvis/events/message.py b/jarvis/events/message.py index 52a1211..a449019 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -11,7 +11,7 @@ from jarvis.utils import build_embed, find from jarvis.utils.field import Field 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, ) @@ -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) @@ -87,7 +87,9 @@ class MessageEventHandler(object): name=message.author.nick if message.author.nick else message.author.name, icon_url=message.author.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}" # noqa: E501 + ) 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( @@ -120,7 +123,9 @@ class MessageEventHandler(object): name=message.author.nick if message.author.nick else message.author.name, icon_url=message.author.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: @@ -188,7 +193,9 @@ class MessageEventHandler(object): name=message.author.nick if message.author.nick else message.author.name, icon_url=message.author.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 on_message(self, message: Message) -> None: diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index 45c0a67..620225f 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -16,7 +16,9 @@ async def unban() -> None: 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): + 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 user: diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 1bd4fbe..8306e5a 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -80,7 +80,7 @@ def get_extensions(path: str = jarvis.cogs.__path__) -> list: 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 + rgb = tuple(int(hex[i : i + 2], 16) for i in (0, 2, 4)) return Color.from_rgb(*rgb) From d4e3d17393e4cd17fd475a8e9852d1ff552c5c61 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:33:58 -0700 Subject: [PATCH 017/365] Re-add Mute tracking --- jarvis/cogs/admin/mute.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index ae1e0f8..73aa774 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -11,6 +11,7 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) +from jarvis.db.models import Mute from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -72,6 +73,14 @@ class MuteCog(Scale): return 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, + ).save() embed = build_embed( title="User Muted", @@ -113,3 +122,10 @@ class MuteCog(Scale): embed.set_thumbnail(url=user.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.avatar_url, + ) + embed.set_thumbnail(url=user.avatar_url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + await ctx.send(embed=embed) From 716f16946db9c3d8d7002be60b6c462c515cf163 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:38:55 -0700 Subject: [PATCH 018/365] Migrate admin/purge, ref #91 --- jarvis/cogs/admin/purge.py | 136 +++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 73 deletions(-) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 6e95950..e7ab329 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,33 +1,31 @@ """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, Snek +from dis_snek.models.discord.channel import TextChannel +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) 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: Snek): 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", + option_type=OptionTypes.INTEGER, + required=False, ) - @admin_or_permissions(manage_messages=True) - async def _purge(self, ctx: SlashContext, amount: int = 10) -> None: + @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) return @@ -44,28 +42,24 @@ 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) + @slash_option( + name="channel", + description="Channel to autopurge", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="delay", + description="Seconds to keep message before purge, default 30", + option_type=OptionTypes.INTEGER, + required=False, + ) + @admin_or_permissions(Permissions.MANAGE_MESSAGES) async def _autopurge_add( - self, ctx: SlashContext, channel: TextChannel, delay: int = 30 + self, ctx: InteractionContext, channel: TextChannel, delay: int = 30 ) -> None: if not isinstance(channel, TextChannel): await ctx.send("Channel must be a TextChannel", hidden=True) @@ -88,21 +82,17 @@ 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @admin_or_permissions(Permissions.MANAGE_MESSAGES) + async def _autopurge_remove(self, ctx: InteractionContext, channel: TextChannel) -> None: autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) if not autopurge: await ctx.send("Autopurge does not exist.", hidden=True) @@ -110,27 +100,27 @@ class PurgeCog(commands.Cog): 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="delay", + description="New time to save", + option_type=OptionTypes.INTEGER, + required=True, + ) + @admin_or_permissions(Permissions.MANAGE_MESSAGES) + async def _autopurge_update( + self, ctx: InteractionContext, channel: TextChannel, 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) From 9aa70752666ecf33559954d93ba77cca8f827b96 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 20:49:39 -0700 Subject: [PATCH 019/365] Remove unnecessary code --- jarvis/cogs/admin/ban.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 7b197f5..1de0b3d 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -111,8 +111,8 @@ class BanCog(CacheCog): async def _ban( self, ctx: InteractionContext, + reason: str, user: User = None, - reason: str = None, btype: str = "perm", duration: int = 4, ) -> None: @@ -131,8 +131,6 @@ class BanCog(CacheCog): if len(reason) > 100: await ctx.send("Reason must be < 100 characters", hidden=True) return - if not reason: - reason = "Mr. Stark is displeased with your presence. Please leave." await ctx.defer() From a9c75207da672511d0a73c6a65aaff4bf243cdaa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 21:18:07 -0700 Subject: [PATCH 020/365] Migrate admin/roleping, ref #91 --- jarvis/cogs/admin/roleping.py | 248 +++++++++++++--------------------- 1 file changed, 95 insertions(+), 153 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index fe81488..893416b 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,41 +1,37 @@ """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, Snek +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 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: Snek): 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", option_type=OptionTypes.ROLE, required=True + ) + @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) @@ -49,21 +45,12 @@ 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", option_type=OptionTypes.ROLE, required=True ) - @admin_or_permissions(manage_guild=True) - async def _roleping_remove(self, ctx: SlashContext, role: Role) -> None: + @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) @@ -72,12 +59,8 @@ class RolepingCog(CacheCog): 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) @@ -108,17 +91,17 @@ 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), ), @@ -129,23 +112,12 @@ class RolepingCog(CacheCog): 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.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, @@ -155,32 +127,26 @@ 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", option_type=OptionTypes.USER, required=True + ) + @slash_option( + name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True + ) + @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) @@ -208,32 +174,22 @@ class RolepingCog(CacheCog): 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", option_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True + ) + @admin_or_permissions(Permissions.MANAGE_GUILD) + async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: 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) @@ -255,30 +211,24 @@ 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", option_type=OptionTypes.USER, required=True + ) + @slash_option( + name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True + ) + @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) @@ -290,32 +240,24 @@ class RolepingCog(CacheCog): 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)", + ) + @slash_option( + name="role", description="Role to remove", option_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True ) @admin_or_permissions(manage_guild=True) - async def _roleping_restore_role(self, ctx: SlashContext, role: Role, rping: Role) -> None: + 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) From 07a3eac70357183c6daf8297549a2cdee85d2821 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 21:32:55 -0700 Subject: [PATCH 021/365] Fix paginator --- jarvis/cogs/admin/roleping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 893416b..2127f03 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -117,7 +117,7 @@ class RolepingCog(CacheCog): embeds.append(embed) - paginator = Paginator.create_from_embeds(self.bot, embeds, timeout=300) + paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300) self.cache[hash(paginator)] = { "user": ctx.author.id, From 6a39b1e8d96e10d3c64fcaafabec4c1571716524 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 2 Feb 2022 21:33:17 -0700 Subject: [PATCH 022/365] Migrate admin/warning, ref #91 --- jarvis/cogs/admin/warning.py | 124 ++++++++++++++--------------------- 1 file changed, 51 insertions(+), 73 deletions(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 81ed296..f484c04 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,12 +1,15 @@ """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, Snek +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 jarvis.db.models import Warning from jarvis.utils import build_embed @@ -18,35 +21,29 @@ 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: Snek): 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", option_type=OptionTypes.USER, 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="reason", + description="Reason for warning", + option_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="duration", + description="Duration of warning in hours, default 24", + option_type=OptionTypes.INTEGER, + required=False, + ) + @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) return @@ -72,37 +69,29 @@ class WarningCog(CacheCog): fields=fields, ) embed.set_author( - name=user.nick if user.nick else user.name, + name=user.display_name, icon_url=user.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", option_type=OptionTypes.USER, required=True + ) + @slash_option( + name="active", + description="View active only", + option_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: + @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: @@ -128,7 +117,7 @@ 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_author(name=user.username, icon_url=user.avatar_url) embed.set_thumbnail(url=ctx.guild.icon_url) pages.append(embed) else: @@ -137,7 +126,7 @@ class WarningCog(CacheCog): admin = 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"), @@ -154,11 +143,11 @@ class WarningCog(CacheCog): fields=fields[i : i + 5], ) embed.set_author( - name=user.name + "#" + user.discriminator, + name=user.username + "#" + user.discriminator, icon_url=user.avatar_url, ) embed.set_thumbnail(url=ctx.guild.icon_url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") pages.append(embed) else: fields = [] @@ -181,24 +170,13 @@ class WarningCog(CacheCog): fields=fields[i : i + 5], ) embed.set_author( - name=user.name + "#" + user.discriminator, + name=user.username + "#" + user.discriminator, icon_url=user.avatar_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, @@ -210,4 +188,4 @@ class WarningCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) From f147da994ad134a572f0a3f579807fc029aca1f0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 00:49:02 -0700 Subject: [PATCH 023/365] Change README logo --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9f01850..119bf2a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ +
+ J.A.R.V.I.S + +# Just Another Very Intelligent System (J.A.R.V.I.S.) +
+ + [![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. From c15510b74d8714375e0be2b3b3117baf8e685a7a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 04:56:20 -0700 Subject: [PATCH 024/365] Port autoreact, no more manual autoreact creation, closes #92 --- README.md | 10 +- jarvis/cogs/autoreact.py | 229 ++++++++++++++++++--------------------- 2 files changed, 112 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 119bf2a..8f33d87 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
J.A.R.V.I.S -# Just Another Very Intelligent System (J.A.R.V.I.S.) +# Just AnotheR Very Intelligent System (J.A.R.V.I.S.)
@@ -38,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/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 6d1263f..06c0ed3 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,46 +1,44 @@ """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, Snek +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) 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: Snek): 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,50 +46,38 @@ 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="emote", description="Emote to add", option_type=OptionTypes.STRING, required=True + ) + @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 @@ -106,85 +92,86 @@ class AutoReactCog(commands.Cog): 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) 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, ) 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, ) 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="emote", + description="Emote to remove (use all to delete)", + option_type=OptionTypes.STRING, + required=True, + ) + @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}", + f"Please create autoreact first with /autoreact add {channel.mention} {emote}", hidden=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, ) 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", + option_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}", + f"Please create autoreact first with /autoreact add {channel.mention} ", hidden=True, ) return @@ -198,6 +185,6 @@ class AutoReactCog(commands.Cog): await ctx.send(message) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snek) -> None: """Add AutoReactCog to J.A.R.V.I.S.""" bot.add_cog(AutoReactCog(bot)) From 1726f008b98227e0c4546848fbc80ad3bbcbb8fe Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 04:59:50 -0700 Subject: [PATCH 025/365] Fix TextChannel -> GuildText --- jarvis/cogs/admin/purge.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index e7ab329..4be51f1 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,6 +1,6 @@ """J.A.R.V.I.S. PurgeCog.""" from dis_snek import InteractionContext, Permissions, Scale, Snek -from dis_snek.models.discord.channel import TextChannel +from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( OptionTypes, slash_command, @@ -59,10 +59,10 @@ class PurgeCog(Scale): ) @admin_or_permissions(Permissions.MANAGE_MESSAGES) async def _autopurge_add( - self, ctx: InteractionContext, channel: TextChannel, delay: int = 30 + self, ctx: InteractionContext, channel: GuildText, delay: int = 30 ) -> None: - 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 channel", hidden=True) return if delay <= 0: await ctx.send("Delay must be > 0", hidden=True) @@ -92,7 +92,7 @@ class PurgeCog(Scale): required=True, ) @admin_or_permissions(Permissions.MANAGE_MESSAGES) - async def _autopurge_remove(self, ctx: InteractionContext, channel: TextChannel) -> None: + 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) @@ -119,7 +119,7 @@ class PurgeCog(Scale): ) @admin_or_permissions(Permissions.MANAGE_MESSAGES) async def _autopurge_update( - self, ctx: InteractionContext, channel: TextChannel, delay: int + self, ctx: InteractionContext, channel: GuildText, delay: int ) -> None: autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) if not autopurge: From da2a64becde9821dbe3a64761bf33a17a166b341 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 05:11:27 -0700 Subject: [PATCH 026/365] Migrate CTC2, closes #93 --- jarvis/cogs/ctc2.py | 73 +++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 381686e..816c478 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -3,16 +3,17 @@ 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, Snek +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] @@ -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: Snek): super().__init__(bot) self._session = aiohttp.ClientSession() self.url = "https://completethecodetwo.cards/pw" @@ -34,24 +35,21 @@ 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( ( @@ -89,14 +87,14 @@ class CTCCog(CacheCog): await ctx.send("Nope.", hidden=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) @@ -119,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, @@ -140,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, @@ -161,9 +148,9 @@ class CTCCog(CacheCog): "paginator": paginator, } - await paginator.start() + await paginator.send(ctx) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snek) -> None: """Add CTCCog to J.A.R.V.I.S.""" bot.add_cog(CTCCog(bot)) From 88b9c63a92636399a35c53ff722e86fe950a1bb9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 05:21:02 -0700 Subject: [PATCH 027/365] Migrate dbrand, closes #94 --- jarvis/cogs/dbrand.py | 213 +++++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 884de2b..cb7e722 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, Snek +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: Snek): 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)", + option_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( @@ -193,14 +194,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 +216,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,7 +230,7 @@ class DbrandCog(commands.Cog): elif not data["is_valid"]: embed = build_embed( title="Check Shipping Times", - description=( + sub_cmd_description=( "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)" ), @@ -246,7 +247,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)" @@ -263,6 +264,6 @@ class DbrandCog(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Snek) -> None: """Add dbrandcog to J.A.R.V.I.S.""" bot.add_cog(DbrandCog(bot)) From d4fd670cc92372f26416308b343c9991d43f6cb0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 05:31:42 -0700 Subject: [PATCH 028/365] Fix typo Snek -> Snake --- jarvis/cogs/admin/mute.py | 4 ++-- jarvis/cogs/admin/purge.py | 4 ++-- jarvis/cogs/admin/roleping.py | 4 ++-- jarvis/cogs/admin/warning.py | 4 ++-- jarvis/cogs/autoreact.py | 6 +++--- jarvis/cogs/ctc2.py | 6 +++--- jarvis/cogs/dbrand.py | 6 +++--- jarvis/utils/cachecog.py | 4 ++-- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 73aa774..7f5adf6 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. MuteCog.""" from datetime import datetime -from dis_snek import InteractionContext, Permissions, Scale, Snek +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 ( @@ -19,7 +19,7 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(Scale): """J.A.R.V.I.S. MuteCog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): self.bot = bot @slash_command(name="mute", description="Mute a user") diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 4be51f1..3310416 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. PurgeCog.""" -from dis_snek import InteractionContext, Permissions, Scale, Snek +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, @@ -14,7 +14,7 @@ from jarvis.utils.permissions import admin_or_permissions class PurgeCog(Scale): """J.A.R.V.I.S. PurgeCog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): self.bot = bot @slash_command(name="purge", description="Purge messages from channel") diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 2127f03..2095318 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. RolepingCog.""" from datetime import datetime, timedelta -from dis_snek import InteractionContext, Permissions, Snek +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 @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class RolepingCog(CacheCog): """J.A.R.V.I.S. RolepingCog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): super().__init__(bot) @slash_command( diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index f484c04..c807efd 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. WarningCog.""" from datetime import datetime, timedelta -from dis_snek import InteractionContext, Permissions, Snek +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 ( @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(CacheCog): """J.A.R.V.I.S. WarningCog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): super().__init__(bot) @slash_command(name="warn", description="Warn a user") diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 06c0ed3..289b3d8 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -2,7 +2,7 @@ import re from typing import Optional, Tuple -from dis_snek import InteractionContext, Permissions, Scale, Snek +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, @@ -19,7 +19,7 @@ from jarvis.utils.permissions import admin_or_permissions class AutoReactCog(Scale): """J.A.R.V.I.S. Autoreact Cog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): self.bot = bot self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") @@ -185,6 +185,6 @@ class AutoReactCog(Scale): await ctx.send(message) -def setup(bot: Snek) -> None: +def setup(bot: Snake) -> None: """Add AutoReactCog to J.A.R.V.I.S.""" bot.add_cog(AutoReactCog(bot)) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 816c478..b419bcb 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -3,7 +3,7 @@ import re from datetime import datetime, timedelta import aiohttp -from dis_snek import InteractionContext, Snek +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 @@ -27,7 +27,7 @@ invites = re.compile( class CTCCog(CacheCog): """J.A.R.V.I.S. Complete the Code 2 Cog.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): super().__init__(bot) self._session = aiohttp.ClientSession() self.url = "https://completethecodetwo.cards/pw" @@ -151,6 +151,6 @@ class CTCCog(CacheCog): await paginator.send(ctx) -def setup(bot: Snek) -> None: +def setup(bot: Snake) -> None: """Add CTCCog to J.A.R.V.I.S.""" bot.add_cog(CTCCog(bot)) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index cb7e722..4858202 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -2,7 +2,7 @@ import re import aiohttp -from dis_snek import InteractionContext, Scale, Snek +from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.snek.application_commands import ( OptionTypes, @@ -26,7 +26,7 @@ class DbrandCog(Scale): Mostly support functions. Credit @cpixl for the shipping API """ - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): self.bot = bot self.base_url = "https://dbrand.com/" self._session = aiohttp.ClientSession() @@ -264,6 +264,6 @@ class DbrandCog(Scale): await ctx.send(embed=embed) -def setup(bot: Snek) -> None: +def setup(bot: Snake) -> None: """Add dbrandcog to J.A.R.V.I.S.""" bot.add_cog(DbrandCog(bot)) diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py index 89be58f..9497bac 100644 --- a/jarvis/utils/cachecog.py +++ b/jarvis/utils/cachecog.py @@ -1,7 +1,7 @@ """Cog wrapper for command caching.""" from datetime import datetime, timedelta -from dis_snek import InteractionContext, Scale, Snek +from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger @@ -11,7 +11,7 @@ from jarvis.utils import find class CacheCog(Scale): """Cog wrapper for command caching.""" - def __init__(self, bot: Snek): + def __init__(self, bot: Snake): self.bot = bot self.cache = {} self._expire_interaction.start() From c69c21e9d4f3be543a4161ea2ad45a8e30c4920e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 05:49:50 -0700 Subject: [PATCH 029/365] Migrate gitlab, closes #96 --- jarvis/cogs/gitlab.py | 232 ++++++++++++++++-------------------------- 1 file changed, 90 insertions(+), 142 deletions(-) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 75f0e0f..28f8a04 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,21 +22,18 @@ 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", option_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: @@ -57,20 +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)) + 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, @@ -95,21 +94,16 @@ class GitlabCog(CacheCog): ) 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", option_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: @@ -121,20 +115,20 @@ class GitlabCog(CacheCog): ) 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] + "..." @@ -156,21 +150,16 @@ class GitlabCog(CacheCog): ) 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", option_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: @@ -193,28 +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)) + 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)) + 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, @@ -248,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, @@ -271,26 +260,21 @@ class GitlabCog(CacheCog): ) return embed - @cog_ext.cog_subcommand( - base="gl", - name="issues", - description="Get open issues from GitLab", - guild_ids=guild_ids, + @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", + option_type=OptionTypes.STRING, + required=False, 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"), - ], - ) + 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) @@ -333,18 +317,7 @@ class GitlabCog(CacheCog): for i in range(0, len(issues), 5): 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, @@ -355,29 +328,26 @@ 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, + @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", + option_type=OptionTypes.STRING, + required=False, 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"), - ], - ) + 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) @@ -422,18 +392,7 @@ class GitlabCog(CacheCog): 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, @@ -444,15 +403,15 @@ 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) @@ -489,18 +448,7 @@ class GitlabCog(CacheCog): 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, @@ -510,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)) From a23c28e5512c381f1d1604f108abccbce8bb8acc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 06:07:18 -0700 Subject: [PATCH 030/365] Migrate image, closes #97 --- jarvis/cogs/image.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 0b8a342..45fc0c8 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)) From 3489fb1ec3d526d485aab00d00051a86dde821ee Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 06:27:24 -0700 Subject: [PATCH 031/365] Migrate jokes, closes #98 --- jarvis/cogs/jokes.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py index b298a18..2512695 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, option_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 @@ -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)) From 93381d7da00d84454967b05a78607a887c84e805 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 06:34:14 -0700 Subject: [PATCH 032/365] Migrate owner, closes #99 --- jarvis/cogs/owner.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py index a19dcd9..0e55bde 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -1,31 +1,26 @@ """J.A.R.V.I.S. Owner Cog.""" -from discord import User -from discord.ext import commands +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 jarvis.config import reload_config from jarvis.db.models import Config -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.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") + @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 @@ -34,9 +29,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 @@ -46,6 +41,6 @@ class OwnerCog(commands.Cog): await ctx.send(f"{user.mention} is no longer an admin.") -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)) From 1594ae32abf6300aee4bc9dcb0eac770e14d2543 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 07:00:48 -0700 Subject: [PATCH 033/365] Migrate remindme, closes #100 --- jarvis/cogs/remindme.py | 142 ++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 79 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index f17748a..3d75bdc 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -5,26 +5,24 @@ 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.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.discord.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 +30,41 @@ 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?", + option_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="weeks", + description="Number of weeks?", + option_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="days", description="Number of days?", option_type=OptionTypes.INTEGER, required=False + ) + @slash_option( + name="hours", + description="Number of hours?", + option_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="minutes", + description="Number of minutes?", + option_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, @@ -148,8 +138,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 +148,21 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, + name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.avatar_url, ) embed.set_thumbnail(url=ctx.author.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,19 +176,15 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, + name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.avatar_url, ) embed.set_thumbnail(url=ctx.author.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) @@ -214,12 +202,8 @@ class RemindmeCog(CacheCog): 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) @@ -227,14 +211,14 @@ class RemindmeCog(CacheCog): 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 +226,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,13 +235,13 @@ class RemindmeCog(CacheCog): ) try: - context = await wait_for_component( - self.bot, + used_component = await self.bot.wait_for_component( check=lambda x: ctx.author.id == x.author_id, messages=message, + components=components, timeout=60 * 5, ) - for to_delete in context.selected_options: + for to_delete in used_component.context.values: _ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete() for row in components: @@ -265,9 +249,9 @@ class RemindmeCog(CacheCog): 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 used_component.context.values, reminders): fields.append( - Field( + EmbedField( name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), value=reminder.message, inline=False, @@ -285,8 +269,8 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.avatar_url) - await context.edit_origin( - content=f"Deleted {len(context.selected_options)} reminder(s)", + await used_component.context.edit_origin( + content=f"Deleted {len(used_component.context.values)} reminder(s)", components=components, embed=embed, ) @@ -296,7 +280,7 @@ class RemindmeCog(CacheCog): component["disabled"] = True await message.edit(components=components) - @loop(seconds=15) + @Task.create(trigger=IntervalTrigger(seconds=15)) async def _remind(self) -> None: reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30)) for reminder in reminders: @@ -326,6 +310,6 @@ class RemindmeCog(CacheCog): 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)) From bf6571d776e5f0d01b1cc407e77f87e236ad4740 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 07:04:18 -0700 Subject: [PATCH 034/365] Remove deprecated mute setting --- jarvis/cogs/settings.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 622a1d7..9102dbf 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -33,28 +33,6 @@ 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", @@ -179,19 +157,6 @@ class SettingsCog(commands.Cog): 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", From 3707a261871a88cbf48df3853737818878711a94 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 14:43:21 -0700 Subject: [PATCH 035/365] More logo updates --- jarvis.png | Bin 1421983 -> 1559169 bytes jarvis.svg | 14 +++++++------- jarvis_small.png | Bin 36281 -> 38029 bytes 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jarvis.png b/jarvis.png index cf2071a7d00d61189505d6eae6ce0f44713c14c6..816408738e28c9ebc62666e9a2b5291721901b86 100644 GIT binary patch literal 1559169 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%0AiZxD}Ki$-iJpOSpp5_;$Vp7Hx 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⋙ihP4y`rY^7N(fdFxlp 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%0D{`BN(W+%POJa?(qo1U(Ycgu;YYllVL{OnbL@ z5ESeo{iPNq6V{OwnG^f~)LPWOhNbxpxb-o(vcqxQ0zootCLNa|4@|L~lHT6>$v^(v z+5Q_4HA{|iM{Z@}y|6*cyE2%b>n8%;$ZI9)XI(ALzX4Q-pe|xS$jfLLw29=IpPo%v zR0HAO)Ldi@`>0MMGdb9(g!nj3Gc4Zf?v|l$VK^KS9@qLQfjRj4pGx9f$v%13# zVUK_yl~fe9nuuI0J_l>44@=Zi(CF_OwjT1?1FLYGELc)huuiFIi}ICjP(J zPQboqXXeV8+w0U%0EhIpc3P-u<-pc@;wjxS8faVh{WTM?deUub0tWkUW%4DJhW1M1 zV?(Icvtue)9{CFe1rG+SojK=iVBH7Lb<`4HB173>p;2-ykRccn(%hY4!^={hTMEc??Im7#hB(a-Ix3jw)`D|zkh9dtRRVA&tuIal zqs9uXlI4b~oV4k%LJf3kMc7b7<#Hk0d!Ba`xi%6mGy0y^Lj>=vK}eBC1iJ{FBYHv%yTm{yuMU^PBGZHdq?Nn3Z>SvpXlRgJh!+j_a{Vs z@@|A)4Q;sTS|;d)^RUhK(0K#4ISy=MyQ!?Q&=f;^;Xzw0jzv&;fuI^xjjKV(eft4XC+Rbam0B{83FSE8-!rI#x;n^|ZrJifCH8CP+v6`AVl`J!l zhd8<2q&e7!A#LL5)R(}sg!P|a;CRs}9r3YcU%T=cc4-mHzc{laleE2{4u6Y-b_7BS z6~$p5@SK5mQV$!H;G~SAACwjC`u_n+h`;*B_a^6k`@spR@r)^|5v;Ql)`GJ=4=j%K zR8^=(Dxv*dyIQ-jV@3@~;OaatMFPFPksfD|p*k{4WuA9}ZF%}$csm(%A&*1jV=m2- zp^A;G>^&_w;CPWPWRMSZoH(1HMdi_lIJI-1aRMO|LIh5yA~zqK6fB3twU@-4_aVV% zEHX3=?2U<&7^xSR1PvXrky-^IA!dTHT)%GKkYRkxmRjqmb`*C_P?TTSkQn zQxgK|Mpgx-aBKnK@R6n-))>wvw4lx!BV6bRv%ym^D3&@9a3P8eivtw_ z5!oY%A|gw~5D6sFM{!Z1sB8ok1!aX9MhLN0WP^ebAS6*%gb<<$AqGhD-M-&zJ?T^E z{hZ%l-rgZsu5;~m&H;DOAklp+e>1#!6Q+UK?QUk3x)52=eDzQ$ZT)_cDEU=x1&|l@ zENuas(?PICXsCm)j7)|*B0O3C7Sjep`u;V~j zh0<`>OB;Q*C?gI!XVVr&w|?&R&ZVzKZVH8ELy!v*+%>=MB`2yWAOLn!;g7CF4j92{ zmXlze>5foN3@^RLM9I;(c60QgxiDL*oUrj-r_wsHu!w| z+;nLmhTJSk+4snL$Gf!c4yU|LPhE4^f8V`7f zFSR@4;Wn+gySEl7P9N9%6P6!+Essls%fB|1){NKY|NIHo1Aa-8M4bnzaTls8NIn2! zH(tnvwK6NW)(JS*Gn3gUzEN}P&nbu>?|PB~1aN7@_rAE1mg0?!l?92PhAM7u<&c__ zIaV}?*;Yi2Hsq~UNxho=nRY)o(K!=v!PCWvY_q7(9G&x`g53@YYG#B>z%>A?mE(S} zB=b*Xh@#v6Bv+9v-BZ!YhO5Ud>8P!u)3%;boCO5Lk0u^W;%boxAx+c;3&&J&tzS5e zaRwoU_xC-yQG4ueeOhhH7_tX>M<`sJ?(QCVkG<5<;!nw4Rb<=($PXJq~$&FHSV zxG^|0{Nzcn>X0~EJPc@jqXfx*+uER&z+nsnDgj^LlTZe2CuIb2fy2->Kbf#d2xD2K+@nQimXw z-~#Kc+=~2bhzvC2eidc%z%dV32DBOPf77HyxBytfz1HAmRDU+$6i$&EyrE?FkB@<9 z-IGPZ>ig;ZSNf~6K6%lfyw4Amr@<=0*uX3uI=)VP0Ay;#L-_`6D@+?K3nCtlTnC&2 zU%!#33cuK4(hq=2B?&qU9rdA2lH~y23Rvo#7l&0wA@%CDhvR0zzwdo}+6PwIha1Xd z;{0L7B4D9R|2T0yteSS~NN{F=f0?0;wTDGcXJFg{Aemp~(~<5NA6S06dkwUz>;_O9 zLtG9X8n-hFcO~3Zo07>%(73gpDP3D2CjgPN$fz6El`=gEYXSNA`t2X`F<5?f6d4BE z9+$}-K8Fkg6RaB&7reIsZd=axzE15bN+Ae>ZzZf5QFT;N_xUpbLELDv%eL+pkd&L5 z6f5>UE)xKYZFi1?C0)1wW3tjZlVb5WSa}=nldmRUbI}ELYap};mTdZW$WeO%U6`Rg z5hZ~&M;ekSP_NpVsh0JFS$No>awB7h2H<2SEF3X>NJsxWe?ZIxWBd29c==B%FJa|l zM`(U-L^d6ixO1ZMi_j8sd%_Cj!aPDAEJIZt=?3keqWYUf{9wl@`lDi1Se`!7unM>V zdeV}584Ei3UduC8vX!KfCNdd1{l0@}_&mpEH9+n0S0-Rh?`fNMu9CRjNzkQdL9$_Q zuvKWS4(~k>Z*}2$&3FM+AH@CiMsf*gL1r|Hqv2&ud2lsOU#W_ zl>VLEgI7*RF?Cb^B0R92bT0#@eomNvWMLmGJMV7%I^EvCWJ&!r&)2UPZ&Wpq%Q8L#hV|eod!Y&WSN71F7vfwxKogoF{t+B{b47KXiA6a zCeK2r76m`%101{_x$kb5Gj4_C3&i-cV*DIas*hK$5yN`kV0G`*-nA zJx>;Jjra1?39rq@zIgL{|D>utLyv4vQ(V_aU1Mh8 zh#?D(6vmkme1|#6$+-Ej@a)_9EX{%gpTdd9ih&Lm8?sS&Q7hWS;PGcz z6P3(ySPpNa)5>?-eWKb=H(tDCIzxKb)1HmGaR3LJoP$+peOW3-4`GQ$!41@j(mRZt zG_pQ%;pVu1WJL)qK>h0Xv&;dOaa~S?Q(WD)KyoqAdBqgvB-9SwgsHT-k z9ymWIvLO|grCW32QXFDg0U%pBjpl3)EKiXpP!_;bG3S>)zZ}@!zmRv4HS4eY~3hmI-xbVK&#YKWlttb-Etr1+0!Dt{`8T zYQuA#>5%!2D52tEHq!+3f}F;{(L`81MU*}i1crnTPhky6_v6-(U5=u|N~FV4U|nk6 znb85zwuSh(g$!2tSOvhAHex!KgT8^FUp@91dab{x#m$$d-pc$lf-4`vc-GS3`551IvP zRHo7zEuxXx^Y=l}b{q-xLH}2o#NeAl2fnn~_5q0=xEz3PVpdo@*%w-R^*Hu`*D?rb z6cj@mIPBeQrj;`3u9)FUAfde;i>pu;kEgl@q0-0Dro3QSDf?h2rk<3g{8wr7|NZg# z-}$L5OGNFp04L}wIp;`Gg%02bI*bnkY#8^y&i8AVYzmwrxlNo-BE6%av}YAPylZ-y+OC1ksh4SEJb zY5=T&u*PnfrMPI2K%>NIbBa%`n)`F-#hCPlR#}O9M%eMlZYj zWrXzdeCcTA=b~n0(k9ME9kM^9Ym8ayAw3InxgwDG0McA-y}Mg}2J<;UVaPImWvDlWnRX-Z{%F&WsiE_;KBqb5tF4kJ z+h)VYPyKa8hDQJgWDwxA{1l{JF5cmY$%~kD1-V+{+J~9jq>ZDn;d1M`n83;sP=DEm zuo>dlw`&3f0OhExa48MauH>D$5-7UkSw{lX*WAzwNX~A(`)f!6xJuTB{%UCXW#dQc z%n<|={%)M zDM5d?aR)Ro4RwzF=OTaLF}bwnVNwZfMp3uS&4q0Pm=b)zb`~^l>4qREgMfww(3)!2 zBp)2x)V~=NInL!k`F>dWeWK$ZMp4pM|74Rc=%qdnQ_7rJHr4b%{zK^H&gz}4REqD8 zrT9V`^f_yyTqL}ILRM`egImFkPf|^w!3bBIZ zX?FJp*UFsT#$|vR=}iy24{OieP&}TF8xj;AWr&)(TH#qDaqz=mE(v_I(t>Owhpu1-^cH>BdnFz-->UGvL^ zfpDoDwyc)-#jXa4QZt^K*A4F(G)NRcbh)vZBDp2XXk6*fo_u{fUa$_xwg@E=lSg2M z#*3j!I?i9H>@u$N4iW1#6YL(7y{Bvt12QU_>676w6U*?PtPfZ~ehuB0o>36tEO2 z+xn|i3KOpIVc`QR zv!G$^CgOmkoX*!Eb&cQ}?^b!J_>3TTm1oO73@(8lW^ z+3HY+Gs5%wclE`9`Wo0OJPmD5bOw}0Q*tL5ze_z~b0j2=GhqXvevs%`4jpPXDDR6a zssWW$(9*HDabQQjkbK6_7LeTXkrHUU%$i(6S@T3WAO`}d-clD>7VJzAUI2jw)fZmz z%4NNrB3e~=H4`yJI(J6os{UCvHMLQP%=ZT!d(6s< zWI5hnzk?g*mdZPQYE@lJ_mbk{yPXavZlmx-<1S{&iEW2Z-hRcdR8&!Mg6gysQq?~( z%YBAtClMS?Gw|;m@sZt8@^~o!bN&mp2B}gCfomR#AoQ$W{SkliZ2Du+nk2!G5lJ zp8`nD#Y}`Rg|I4`mn2F7O2{N;>?R7?O=W=`waJdM zs<+XFSJRH=VEb0KwUHO+OoE&2nc>-#1R6JntaptNHqZaw{W@Q%LmFsKq>6EHqq3*Z zZX|0*lR5#)1!bti1`uBy%qQFfoW7W;LrE=$G~+7{^rZnAzJY&jGqgvjdp{-zB{zr{ z!;+fN(_uAX!=B|}Tu4mvkU9OoXaXa~Ar;JSSYzG4hlhni#w6e0UC)eDk~gCc$cTE? zHfN+)6P67y72ExO>}5L-DvevRGl8K37DrN;#L$7^epT3Mml+58a7y+~N7w}IvyQry zRqi&X0%Zec%63CzS}v@Z92zhL2R`xsu1>HP#H$HI>|7{42b#+g?^+48n+c9q*9G!v zSIItD0lxoi{3)nhf%U=8u&`cl5I&zT?FJG!H>PEJe_sq_s=wiUfiDIelSS+}QvHPt zw_aa1mu9X#1H!vQ=_9H17Qt34KCTn3rMZ8*QQNj8Yp6b=Dm;y=M0`D6StWM;nPiH>Ces1CBN zdr|@*Bg|ir2;Uk=SI7D8Y=3`9U`Ue1(*|1v+;(+XH;8!D62MquyOUGBVbMBRUz>Cw zn*`E~uyv1HN^EN!Y-eyra5N5#e2GkLCJS0dl89~?2MtmoV9gm6lAwtk-~(Ch{^FoH z@qv;fEHHWj=~pkV$tRKlSV&;*TS9@1l#qm1<$$jO3?OI}c_ysx=Q~rVU{YDFEvft%_F6Zn_+1rpd5(&qtOeR!F641RPlFU~1*afQ z$uGBS%s}LrCb(+p0JOl3d>AzX?IlkKIsr+q5rL36a|gt+(0IM6T8+VY5HbO!$1>CY z*}VO4!H`5l3WloR1zz0Z>5ZejCeyxJ_(dwSH*FCB{Oez^X zgQvAoOACjaTKpXni%f46^$ds7a)X+-=${C8I{YJzFu6#;rm08a<%|A)*lNkMg{xD+ zAZJ<0a4Ix=8}b$4qQ#vHnGd}QLwf(b^uNvdH=I9{EC7*{ZPk@F!m{PrLS~tgF)@qw zf;Iq*J#Mb;lS;M1B0cD4#>F-DM5WNMWbhpIF|1KP-R3IoDd_731-i98Y*N9=fX!|; zRLk#yvUwu8me00}#Dz`Z{5SWVfS)3?{9_ z9Ztf_u)v-qAxAM(EtFP+r1L}Ocj1cgzcl0%l(X}4(tTJVvDQ#G0BII9%zR807CU_5NzXz#S#&jpa+F!{KGHi#CvBr{?s-Jru4okZ8t>)}QKt^KV&obe_N3Oac8<;5x?G**EOxTbU z;(0A>;Lzux?9|5k;BHvl;*Tj@l$fwxry>m&H){`)l4f&oZBCj-Am6W5^0XIilEX0+nviSwC3AWxeII_R)H_>1ujSv?4hDYuP9=EmSU z`ZKo$aa(n@qv@b`@MHIgB<7Gl@OB5u4lr+cztBhGAh?Ezb!~xB1=(07=PS8+D;I*GP#0F( zzHq@|Dk;0=QovkhBKZk~X zZWm5aL16U8XaTgS9gOw@>lr0RUE|xK=8GJpp5GL>{Rln(qTDt3cUN;b zL#+y_KKd%a5jjFIz7~39WJTT060qFuKfw}nb!ZPDx$yOAzDjBcVsfv5eDMB%=!25 zW=o%eYem8o$S(MoxAp?58GHEl&5UZ^yU?d>yATrVGjHJV6jc?Gmh8QMr&B=w)h zxc=6BZn)Z7FCPP$>Ze#+UkhD>yuUsK_ZN{{4UO8z(BoRzIN-Qa8_kg&j*w#u8>`0# zl~zip$;ZqvuOOq$i)+l~O8e>%)vbaJLOJN9-Fp7lwco9DB=(tg$73tGcvmkUZF4Q{X!ZR8sfr#JRnlwSOz-*6F!y47 zdlp$IUsenTP@Q4k)EsC&n-Rgye) zv@)8FAW^7#6o@6(gz2jXqsw2{a?B zxuWs;(hdMSpP4hPtZ{~R2nCh{8>_~YCZLh@oIr=ozYc4V&VnjB z;2CNKyH1bFvwVt@n~1t29j}dmE%48?>9bdM)*%6~xb?gh)>>#%yuc(UP9}R*bql)c zy`nBrscJ~rN}kY%Rnrwg!%ACZEUEFhBG>@2u1e_;A#vB32-dsS6#1%vdS#5~P~>US3T;7-(UndK z?NpnFk$6AM9$FRFSR$KtngNHfoGCvy-rdT8?%dXuE1jn`?t7iT8Cpo3 zm!A_>*(kTp>Qi=LLqaWJt7#sQe5Ip9{4lVUk_)%>=-^y-8U;~jAnqx}mdLmly3atv>jrOrMbTuFu z$-^r6MRNI9(8>OGN)%XmLG1bDaab{V@Jt>7`un3k#-VBqWcCiS3VDzxSBf9J>lee4 z?zj2U*MNm{W3sy;qf{iz(jsdi$6EuDDki8;1zJEv}~k3rL#HOz=<~00phko5gtr3GCvw%4A*LXscy_vCQX3L(9fOrqb5@ z)JEj`a7ff&LEv9>y=g4^nKcyPZ?@b(K3c7*T;y7ap!RH8Y{NOKCm^ zY2q9Iw?1Yv)o6kslO@mGrc>+jkhQ|Z^ys}^g(3n*An0s)ph?~6t$+2|^~6i2{LAVV z84n+Lw{E{9;ELrb32N6&ee|zB_*T>6pwk@@k=C4$oQISff2!&iVO(5Y|My1aPbK^f zh%>waOa!}*Roiey{jef)^=av7`h1?!x!G~ZWHuxn*HR6Y_U8`2e%qoAjoVTkLoEL< z`*!j?LrY-|@g}oMIv8kn>6pN;^e;2$jD@`3Rw0)QJ>_C~r}<-BPH8$*Pn%~1OTFKa z%{$=HE3RV_o9NZBIqSUD&3zH+HDGlb+i?>EtH2i;sQg^e7I?M3#6zcE57tXBg1|6A zz9!zXr2j|`P`o&QL2X!hTX=@H6nZXYUuVD4@iNDFG1Bf^$i7<00P$dbKaxXafm8QB z*Tt34eo>9LTDgWMcpb)ub=ou6QpFkINJM>Q>$pDDtia5K*m|X&vgHmon+a`{qE$wK zLzeRS`3o{q&-ufKU>;67w904WN~J3Db|G|)?j%giAe8CFhq!Ls$4vwBYR z+Gu(gurc`gIH7!GTFuiAJCztR*+0QAOvy82aNT1s` zsTOix1rylpC{H{_rNYu}Q`LAh=xL5IF#~DhHdrb7ZM*OaD0KAnnjmOCNIwXfpfyij zhIle)c|-0?Qd%fa;;i`jleYXf|GKZZ;-y=exn7z9!aG`x+;0In?YYlUX!94Ut@aOl zUBZB_Kn^%4%|MR(xKTo(m)pOF1DZIz?*uo@pOz0Ra)_#ehWu!K@I7$=+|hJ0ckm~eIY%UE+%Q_3mO$`Bg{?jja5)R1!m>Hj%R^Jz)b{JW&) z_m>ZyL4z4*v{{|I6rK@rN+)pYAam5PdMX^Qii@ey0!5DJUQ(F`OSVmB#3GOnooTSr z8(-i|0uL`V@GT+0D}7$QlE)%hQRI9^p%V z+FOwWqo?N;g~54{6I%~wu-!A%4fcZtdHGvW$syDO%X8%_}&5`_=P zo?2!*%%95*qK^bU|1N8L^v%5w_CGspo~Rzk{6a3z=x9Ytlit z_%W66;Bnw)0{hY@QYy-KyLAHzwPXEAnRNKge_JH}G(o!icXoRgFY~NeR+I*l`ll82sK4O$W`WOt1P4ak;42k6|$u)dv0Q4_L4x_xmR=ZJ@d49c%79oCioep^of za$y;z?+)xTe_L898%25rCSVp(nYc=Erf+E!a%>>v=0YTfeFVg?Vn&5OGWZ2oR<37eUNzlq_*4&TWxd9#(ie*GOL8|@+Pu7TX4=9Ie1`BM3nI%`xrcdeAd`9>J zuauBL*bw`;8%62V?77aO5$HJ9QK&Sx@2W^{k3dFvA+y&=oj5kQl@*EYkRu5ovqL1t z{%eJw1+5X@Mq$`%Vc_*?UfNU)1#+S8104kLIvX)#A+qravWy--U-}NHjy5?7qs%I$ zTzgpLcBYHpxq`__ePuVKjk~xuS^~yMWa;o&EG$23irhYK|MgRf9!%i9y$?}o?u+qK z6B&$vwdH}CZnhfgV4C9^l~Ssxn+;vQZT%$$+~A2Mb+}?^ann-=BO8L=9y!5@>;mn& z)|xM08{G`H6sS#8BNKj5nhmW8}>n9Z=hVS^?`=@JBa zV(jEO*fb=4xBN69zBoVq^~`!$%vN}ojVfKodXyI&0E^j)>wI%i=c#h%*wvA+0=F|V zHMD1u<{9viaeEu&en?oCYBTH)Zo!i2=}IeM?MQ=~v{fnQib~!lerF47et(k=kHmuT zFgAT&@C)rr0Abk&^#DTH7Jh6E>y9E-t ziwSWoGN`9G*#of;Tw}y$u25;3ou00TTs8-1%@MD~0UI{1ssZJK{48iatzLx;EGs&| z(gQEp3RY9o*He|whQP@}okS;Lq4kxE#9wJrINJ~~(UE2?&IcB| z^xej#zzL>>v6B`&23!Sb;BA@Tx@ZOHp%II;8kVU@kk9%{7MYtPEsqEfPaIg$@ za_BoLyu)rCAN4WsvjH{-82J1@giYIp3&5pP<=C^6eb9a^zQ9+h{6)|sPwdLUz>W6A zBgemiRmdyLxU-;r;$OD#w2x)Ali?bM4MhV<2l|2l28^F_lI(ys0)N>%f@N&>iZds} z*EDK4;O3%iz2*`kTqfv(kLEHvDjV7tm_61~EXMe#Zh*XK(3<91VQrIrbf{qXiMpD7Cc9|Y04!UIM zK2U=J6Oxhf5ZKruT}_}5HZYO8=q>0q&Yw`)V#I4{Q!x}@hSmhV!1s-wwR8|Yc2^uM zxPy+AW-#EF8#qxUuv)dR=}xHiwMjm(y_4NVV&NfkuY}$nrOm%c07s|78cQ1uIu0-% zNKzO8tp&-~Zk5D!C>dXtnCeX!^MoEKDHu?yv(k~#YBprfI3-wIj#+LN?(J$4)weB9hPV z^foFhfCU#=WGGd-j?F8mFC4m)d`+edtF@d3J3or-6OXXJU1JW!`sKr%AYb@%G?ARQ z90;kg%wr6l2_NXwbToz0KnzTyDDJ}Y06Eil0xY9dYlqmw9?gM-Tc-hw$r<&#H9ieZP4VxOwK&$rW{jH$sRGn6b~&eY+ypAk%y79T z8v`YU6y%GX$D29@0@J=eZ?$@0e{6$#BXnt}&lY>EF}U@?ADR7}peZBG`GImWQ{w z)>pqgIqdh#L(LDfe%ya`<=nmdzd3xDl^{yqVV+fd?f9`R`%L$Jd2R2ty}wc#hntL= z%QjbiWqRmhz_t0UYR%ouvK`DjEdQqzuYo4+t+34^#_^QU)F4GE{eERSZ_am9;5+T> z_(Pj#*Q;KNAENC2RHWR$;E*-p7aUp= zktsfO|9AgJ!pdi__I<+6+jJd|K8U{vi5ff0P1WPnpQvs5WznF>Rd8sEU!V5%lS_3T ziL*EAeZp*iVzO{kBaXHUN_iHXw8}pdxTaO#6r^0n`g9Br9|5=6Lu*Q+#;-+uN#bnDF=?KQZ+u3x_>j4Dn(p6*?G?V4{Rto(7+XkAhq0!K;Inp!hmO zk6~$Ma9lY4bNaHC>4GAFs}({A3`hJk+(LglbcFsdP~Z|gJqhNFq=i$^dD_<-RtBrN z818sBD)9rftH@0@mWx|E?Kh|R(A~5zpXhw`ar7sJXz7cs9*Ts9b}8Ptwf(cIo~f@p zYtNvs?#KZcxhWyD{XTU=Cp5yG|@>J8Y+eVL$`C_egFC${DPNgIF6LFE7veZ2l+$ zOyMS0o0teEFdmMMocVx$JZLM3U*5pYGN-chr&!dqug_bo)8%ng01<3C6_Ral8tu=+ zS3{}E)_aFu*@Ub?yu7?dy)+V{BPWkm*6ii_gAff3lUr;_mtgpt$Gtd zWz(kp_7oqU_u9!eCo2(D@`8RvgOw!tI)pKyI>pzfef@4ry@#w;Ni;7TFX(xV@O;Cx zU!3B@*WR0gtnmBS@XSEhde)%s*m%nkYZmdjZ_o)w=#nYEJ?-n$iQ)H&7$8WwX&E4X ziUd~2P8^l~J6C_>wBMfM!`E97%npM;m56RcB7|wP{~lUfjT;gK6V_ zs~{Dxjf8Dltw$J3|26G*r}*%l(Kq=zAVo(H-mV@EUq5%GforETjMsufH06H6wo=1| zgG?nseZH<0SKz?m$mNI-Y@Pz4XpHK^5lbgw;F9jI@1(*O^6YJvSP~SB={j79R zJRg$2T_<{#S(eX56hI-kysEFVr8 zo$14RK@rGf)Ll4iYE<&(;n&eYUG(?6(ki$6a;ujc;5ueh-6p zg0*&+EjZM(wWEJHMY-KtRcml1hpE>dwdUNEb-i_uq|9(pnG zC~|+wA01LF*H|bn%{C1449yL+ z5t;9}qP<=vDN>fTR~r-4Y)ic?wsY=WbiF8)z)whT*Vn^%JzYHHYoHT8xNS*XvshG7 z(&g^3X1p~mHltA1cF-=jcvImxp{+E$$jcy|#`EE1WPSXZ^{$&Um$z5$B`?=ovQ>{m z4}K*qZk=7v7|0*L7AP9jdx}r%+RWb(Q&1qUweik$!1G@Y9ZPicdvh{mSn=xpoUQ(q zc-BU7`;TfiSKF*oEBXUFUCMI_DpvRUdFj_j$8RLMW$CYrGG}n}jUWCIrnsEemWE%t za6*4M$Ng!6bZE}gx*I>LwIb2L=(F@F@6$FDF6`UYXGM<}541M>Is{zoX<54Kpq0~B z4&BqC%#P5KeqEl_&R|n}d2IowQm^O-biXb&wU!NE{)mHidWm63eL(F%t8;bCYBC{( zm`e+IDqPSOTG2sbJ{o^eoVq8#gn~a3aM42?d1I-XeW-#+bSSgF7|3_@chxd+r2U@5 zXW_YzU#0X?#zd!j@m6cbNN-Mx_xlJqx{+Fp%&08FWq*t|CvUt1@0VZR`)tnhVugZg z=@3A;h%1)xSQ$>C5~8YFJ{&A2K6e>Lb&6l%&T45Ek z+IofgCjKsoE^U)TnL|gtkCYubx!j~o{%A5p`mLWBFQ#UGc(%$8C;sD)Tlkg!*{9z8 zJ9kU&a(f-!Ep$pj(TOS-Q6FV|>|vX2qs`B8LA9d-fsajDyj=7n9G2S~7(pB>&2RpKsgQRdJW)8{DX?o=94nd7WN- zq^tse!&JnSZtpzA7ZlCSy7I6#d_bCo{9>y}q=$Roh2Z)AtEY8Ld)=5WVJLI9zMUdm zs)pH=>6Y=rB~jvB5;m#(V_c+5TScjh^-!jLoVuc**jy{C$9!*_Pt5c1($H1?yi?7W zvl7eB9C2}^6@*Q;)wOilS~ytKjySoV99k8(cbU918hbz_?T{?b$GaF~;sQ!D%kFEr zMW-eq*wn>TUF+wKWEPuet-E5|XwJyC%&P09`=0T3SwwcTHhO#(%a-8uoruP6#0PZ` zGY1y+_6!XLO2aD8czW|Q2NG@Dp0*5`hP6eTrwO@5ocij@6aA02bdNZflxsEjwn+oG z+O<`rYX!G0_^wevUT?9UH20ie<$zrbH$OA%dxoWsYf`h9{Z%s4VGtX#oQ$zGx;IbZ zzN&va?Ox7ALZNm4kNqOi^O%QiZC7SDCfUW=*;nb49cf2?RPAwY*QIGeeD&Um2Ts}q0HL_QGj4wQp zHT%78x)aM^ai&~PKik!CY$#2OM4SBmhN^z?r1b4YY4O?K%UP>qBFXatx+Yt!7n&3i z9)4S;60l$VypHiy&d+v93%fx$jt%4wX|LHbYpYv#V&=f>A7-1a^D_-RDl+HUpk)q{b#mAHdN%evk4VBSQx%1d+-uY>j;VGq=IX2C#7+-y zsP>qZkc-OK5q%r~paj=b?8xge=IR*N?nC`Q$C#*xc^s>%ZL79E>2Flo^4`A6%5uG$ z&)}Ntsa=(a?GbsiAmGztlHA768Z%1#oWvU!CpKrV_YbfYb*@?*d)?VVRCY{vc_^Yh zmByr^MZDO^`yawBa#D^Yx?II1yA4K2^+xTNu0t@4vs+w3HXyF0tCB*FRq3ia+ld&} zZZ6dEHQ&A0N^&+B&-~CfXHjIv*-Jf)bo*2Fj1Igqt-GkZ5?qHCMfN?_Szhq$t&7f#)lvCJ}68cyb*7i#D|h30WY7XUhuwb zGAlUnUyF5oN$tc+Mcx>$XiO)?VAANx8^M0d3H?3)98G;#{lK1smuNf)*LZf}bM$1v z%i6OnfEPY+Nd94$=T*Vlq0zDKV%j{6+bfGTybu2lOLdj@u9gg)BW_eD5h({$`21_! z*n8Wl$yQ$CVEr9l1BU~)uJ0x%N11eFsmetSDQ8nRXYP`$lROZNw5gYI2koDSCWq9@ zsr-uda;6)Bw+uJDGH>sn#EU6%jDmPRY7L7;?r9cPjkM?-X(=0aV4JS%NJin{huj`8_s7=JT+m zSKe{#({ZgjMrY$%bB7E(N7AV&nCQz$wE89x3xu;BJS`#kykO?zr5%htcSlLGOEwOCeU} zMG?5N%y7Yv!6%s7oEYwV0d8cOewa?&P}>B3b+gZ_L8^&Ry-Nqt@`$SGiR-ULQrNqP<$1 zP3~?@wrkSGzAd_cJwrE!yQQ!qp=>7Y4bSsL0K<2iL&IgFbD7sY-y}iFqP9PVORGBF zSi@?#HT035fBA*>{;P0hN?FC3<_m?wAZ+_PJ%j8=bJ-@%en*V0Om2Vty^?q5AD)ot z7~duMML8K#lOq(Gsc96$jG$NsGw9s~n!%04$8(KOrXTPt3~jwwm-h}+|3Y1#VYn?d z!$tA?o|D9L-90K?QP46)h0TtCAg+r!($-7%2ZfPen!>`O;N?e|N z$4aG<`<4_eW?s(>abSiij)o|%=#uqS)uXvv*ERD=G3Je2)A7d-g+cOGKha~YEedKD zr#;Ds_;a%^Ly=(E)Erws`#m={E;S=HAj!nV;X}B_1BxMC(Q21L2}@b-NO_q1VzQ@H zt~(GY4`7DrJX4fZ7}7Cyq91MTW=GW>JKLCHwn+SkE>{wlo4K8=btg^{=0;SP6=!OXa;Hxz!Q| z$uxzzAZ{o;e09I%kHEG+zM`#j{jIv}uG->6Onr0Pqc;=B?eS+)m^ORc2G@otG9q~< zdd&jwr@~NQ`>U!tF=T!QxA$N6dd)RKyv)|k!pW4*n3M!=u(dPG<#yEI{R&0lb8#vn z$GnhV18s>Wn053LZg0rgqs$P?6i$$Eo9jL4joeqU+_CQ5SGHB$;Wb`-f2|pknmZ4ttK00|EEH}FlEx1RHx~B?W*Y9bZ@WY>z=o$U zj~l8`OdbyTm6kSCs&CTXua%joC4B$u?8X@B7{xF(OE`JvagLguo~~b3tGS5awpE9= zzF9D2`20;vn{{9~M3 zO!WuaL*d5$p2~;9ZR94&QhPmeZM8TN`Nln|%<%amLpoz_eQ(89j-tz2%6nBoV3m%R z8#eKnNKo)$mU+LFwo<)$Y2DNQ>X2jXjI8DUN&47_`u!W(-F7PG0ZkKhPZzv-<#JA9 z^^%wX#nZq>X1RxbTLmp=qHCNl6|i5%u%oL(xOs+qohudUtX!Vmkt!4aEEUy6TtCyS zpD~o$CwdjD7_p+Zyy+45wunQLO?)_FCIa<_W=Ch@uS=OjuNL)K^tPOy zxEJVMDm(4G%p_UDp&vm~asc{wOW_t@>na9k;Aj6yb-} zc~*2kbCAm~NUW;xTT0{yT3B6A^*pjHptf4{UU*s> zXc8?@Tnh`eKk3gu;A%U0oUI#onQ3*yJhiSjtlY(}QsCz&eq%b>@F-L2%ht(~y4WMf zED5+mhg97z1kZ45?2rER();V=wrm}Nxp{xkXun~5T!eKarlc)j>lyj%LvmkWW!Kh8 z`PK(-#(e7Qd&6iBot&b!r+%UmX>Xvd2q&euxC{wCl2pGO0jZKU6ug5k5NLkt=Kkqy zkq3G0mPyyHF-a5eL(v<>(M{Be-qltCRVW(K@|Rj!qmMw}4Z{7)^8?ayv1vDF_F!gr zZ{3SzR@MYAWy}CYFL`p+;Un)G_gtO%cgX+cM}FF`m>dfKaAVUm$wKURG=mh(k%+=p zH=BR%$emx{K(sj^1j2Mbci6tX^85FHB$QpuunZ-jXdhbc+_Nuk(aO31f$yIK zK){2yXXB3R>x=_~B^LxqV+LAN6oi1+$t&a}+q;GW@bfrEXV6{bBUv zv2hfzeGA=Kh7Z08TrnL>|D?YeCaB-{s+VQxG87$6iuDgY{V361W@B4X{(WO-t6KyL zg8Ln6&?b_8=~-1ociZf%Kg@; zX;|OyE) z^acF0^u1oP5Nkk2Yham(lF?e-=qUJi?9k;8?WnCtKx0xmjfld4m_1JRS?+>X6W5;I zMV3EVmO+a|1Nz=KG8S6H+BH%)&%~&3HOs4uS2~6M0k89Z@j7y4#P-$u1!#XDaj)me zrj#)!6!RaxhpeksFGg$nPBmtvqJ{fI5A_T>AC_oGo0qzUU+e-bdWy%6^P8TjqxJz? zw6em@EJBS?%vfx_`_22hZgh4uz0=pBlvBhA`ezk!U$$dcxAmnD;iy6X{}$8&1@_A!*rZ-w$S7pEnDY5!%A9o2Q$M z67Hi`J=A0fM#?ZX-sC|Pl=z`x*n+0?B*`ffZW6ijX6_7m;UFGFM=!idvk%9D390#zYZueS3W#xfvzzE!d&pZV_& zU!$?mL;vuX-|qXtN%PreNz$4x^$+jrAp99iG7|(mX)Ew zi;776I~@PRv-|B+kq}X+;^|q@`o`*x7XJa@?-sH30Y@T7;biloyxaS0PN9NX^sU8} zabE!$g>}Zwd&&+zF2h;hLPLTFbKdIH=qUX5@Ufd>7Fulo#V1rq7%0?wM@QLN{*7oX z0X4R&=&)T?u-xqx8Up;}y`ZZ+PjZiqL18M!Ha6n77NC@^oz0^}RAMmbPuPYQsHt<@ za~w+X+TTQ)hf-$o`nB)#*;Z&skiWSb@6+&{zQ+!ofaNw09S~%o5ZqZ;k4~OBuJ?B; z^&gBJVs)D9b6!rM36sD0d6x_=)5N|vdymmbDD**Xh0d@MTJn}}&C)^#)wC~cFbJL3L z_q4a|L1%CA*zrLhvVew$5cATvE6$>oF~)~Of;bd{du5@ci6c5=^Vo(yA`ATI$o=(i z6dktV0!IZ}xMsLJ0XPW}#SWO$`!;3;n4uJe<$iNSR%3D$MWECByi&pjvA2@xOHj&h zJ9~rTgdhq9fz39sXz#<(I-ikr$GA`vG{Sp7zD;)(%0L9aRDxGH1L64;kK69&%*JAl z%K}h(G%O`Z+WwfWfhU?LVyd--SHhgd4jAI26KAy>OT7HIx>|XXun501eD72{+Vlpatsvh)JWPD3~Yj z?3=uTcKOTv1a9;aNePOe=~!Zee}z$jzc$L)ap>nld>$&u3*wueSEJR4x}+PNL76WK zV|Gb{Cq-$@?K-#cI@xp-jSkC`o03QxDC4G_(kvPY1d-Z-cHJwWTD`g z=GEEyoTZMsx+po8_?*Tjqo6_T@lCH7X!&)$yltWth1T8->zfpjuO%y1q9yL#xKNVQ z6!s8!?Qp+lya8o8X&!(mnK~Mdb)J2cx)-H8^JGjkqoF|6Aum-{BuaZw@5>eD*q}uA zC*C!*;?+P&EtjXWNoeF?Sgx+Wo<^%0X%Wg$A@*f3dvd2h7lkR;+QeFBf;I#};I><$1G}YGdJCm3AAB~aS4K-+ zKgG&{*V!-1B1lB@<2O*&EABkV$>0ijvg7}3bls~wwi?ezNngvBgrgN1k|-3WOfUX~ zkt#~Ck<5#6%&btl1F+;N9vv;xx7Z#Vne^wPce8C?#ud>raxDw=&m%ChOf>g^n4ks!3=mdoi)$ zd37iW4Hed$5Z^#cg+r~`x?{xLDF_O@e(`d!p5cbX3hPnE3IWvKnY2_b0K^oJXSX)e zn$cEmWQh$!t~3$~u)gQxZc>^Zw2 z{L5v!e_WqeoDbVLzFtu# zsvO1W;YPtJnd3hTo%@B`fx$Mw;e6BKJ=!%5&~1hL$_KAsL9)i)jN zrc*Vma@0|9aeX{7-q;Pe2yr@s$jC>>ni;G_BkP>*E_fz>sDEANwEU(SRdur$g5%V#HT=5QS~56+a#2qb2RRq(YS< z`NJvb3B1nhtJ^>;+bzes#e5>*wAzq}jV@HV^QhTdW{v{qug*Rl7NI3XTe`Co=`euX z6ps!+n2l_c{d>MNE9CPfCet9@GSn7e8Q>$^j{@QF+wp`_l!B(kuyR&dPk|)x+Tj;M z-!-(1JG6_KE?$bl2QPXR;4je>s-yG45BkXwVUthvmlLr({qF$erg+rKjF>!((h=L; z4IFpltwsZN_#J<>uGYp-@E+pnl*61oFgnt1g<|uHol9qTC3vCebYSb9P5R!q>!JYL zzFS@T6eaJpy=F!A-)mP#1(u#*51j5lo$7h7rRIodqL2u^)~aD2w91ybocBCOpCU(b zB6Hq}xhl@OTE~nJqx3|Y-tnwg;{+5wGjZM1(Z^`X`^zrlGw(Vc#}&y_rjT0Tb>8#x zm4da==nRAhV@~Uw9MWpw`%^q-ml>zy&@%2GRbjE((wHn3ptv(nXYEGt%C2eb+0CN6 zegnlhCMeK-cj;ojYa!5ZxJM!XCoFktcw`l;poA(nb|Bl?R(*3(D z%f;1b#G$?)BNxx6#v2_%X(MR=qUe6qPkCBy=yCmPFJsZQ&m%))ezV^9c1 zv%|kcvrr0gR}zvkIFZb!KofZV`rpHxnq8Vg3`)<;@(;%GyioAY<+ibK0Hv|y`%mao za(!i5{~Yt2k7&8!tg934j27R=Q59w4EEG}^yxZhpDtKGkG}6|RJIW;Bk}HeE8E7CM zyf!$3mK46Sfib-t#ZLf_rg;4SDEktyCa&#qTHD&zg-TngqNbu$EehfWq7W@8%IXT3 zh+!$pzSJPHB%xYCM2uKjlua>$2qP!~lr6LHyW~kM>J(asry386GJa(vtuw8{J3kz73QzlWU3q40`J0l(TwruZym@{WrI3AY3SKIfTh?0mm5trg8BPQ; z?r5_=F$gt%i3)gA9zWR^J@FE@+lvoA_Vbj_2N2=Z6ZMd(gG~NubNi3SZHL5c4*6^J z=fpzA*(rFbSXO?w-0YA6QF(s{6jBY{W5(u7-2kFC*HTmV04Qpy&&KG2Z7MLF^4R(J zGI_GP;D)*4Fzj$YVtxdF{JaA72Z*ra4xJ)XdqU&HqW<=_oFne@Dg(7OuEkvG{kr`p zmy16&9U7W{WU0r${@P*wnZ5Z3Uq&+G$XZ-H3{`k93V^0eY z=N%ci6n$sO2Xjt+`FgbUM$AV|PoIYg1XVrLM1oG;cX9ClHWgupiy{Ea`gqFst5~iq zH0_=`!TTMX(uECW<9{+$5AWh=IyLX?C7>j0JzH(^!rv~j7zEvNMdt)?d*<2PjFGVr(c&rCsaWP; zD5)(4`tTdBnhp=54WFq1HRZA60r}`iD@^rxu^zRZNIC>pp=Fn*CMdM%nTolZKE=O; z$ih%>!00LWq(yo#Z-VxyI=r{mfi3Ar^B|;{;a-~zJjCTlJq^;vZ}TFX)C=CUO6CLIsLb z9#^)u8ix#C*XgEC86)@(&`Xiy)V}KJX(;{)Kq$PCv$Cr|`Q3j@tH-d2{o@qmR4gl7 zONX0ICSI%^2TJjmSk30=_%r~*TGzPkhEPfzlK7VzFq-i9Ab_u*l{5q8*FKw_A`;>9 z_f&lUl*d{}yu$`9yOzX&Zaew&ZHP+^_LSeNSXNfUgI~P;9^OXsKD0qy1+XcPTO8Z( zj@7N{)(27|p^Q#bnoa?rV(Hu|tzxMFqGik1M~MH@peB5j}FG zS`!i+KIP}Cmda7`4ng+k zecE~SegD{x?0@z--ZcNZh9X7KTZXG#X%g87n}Gh_>{}sYmtEL?m;*%F;vTLRIzjt3ctT5-rcj&)3*7S`5z!v}%x&b1BSj~1u0*qG=eRYZOJ z{E6ed?lGu5Y#;KGKY#mMm8%4E!!BsbNl$Z4xC15tpqg)vCo!fDLH-|WKK0KLPOOZ( zCS{`#&jSd<%1auJjxZ4$5tP*06)C(_e*4=OR=t1T4&F9zmr44rE+VTZkE!4k2-!7t1ob%pYXWgx#omV=Ce>UnP!{FDgInbt|cWnFe1U zPG5C9ak1_(`E%6^-HfxcXRr-}9}-j*eiY+C$naf}ZQ8w?L?RN55Zk44bZDk(EdTSy z`NMbvDlkEFLzZ8iH=P3*vJ~+p^I3Gd$+fexMj#~=(#{DP+5o}$l+>)E5XLx!b<{Ae zG*_w*aQz$u-D~|}(#w{=;|G|E!!Zu2$v~S=VXt?rJr&7N5xfe2)2I)W$KhDmOS*ykqXxFOJ>a zx%15FD<{90^My-P%&)7LTCRSM9SG|UKi{qW*`}v9(HdKs`3q7HG_i*<5UheRMTLaiHYV?wh`Gqq>}z{IqO^-bzE9Y8My&15)DNW;=P z@ECwW&dt`4jKREZVP7+GCy!Ae(otO!$PR+-qBL0rik||Y>Jy>SxC&A2OE3Z!1 z^O66eS|TRjY}jJBXrsW~ln*-KdrJG<6g#l#5Hf5n2~;=($#6SToP~%0h0T>8eH%wT z06KQ@jim;HThcK=6s=`TJIdS-#5JyZ!WHWwPfke@_P%NP6brAs-^AHl!;-?Ybx{Svky$#0M(1l*(Vvqr$tW;WI)o>cpHM^ zxFc<{J1C_7l1L=Ge>?bMEeP`VS&bHS>Ppq9PQJ0FQ6#}YtA1dp9vuQ?@kZS^zL+FZl;e>l$?34#VtOYa5G8Bn2DI;o}^yH z-lrPN$v6F%H9|0$O0**}Ww)it0LD2xl)K(lo>*he0Va@@HW`VIidV_bqW%yWc5 zD=UX4Sf(1<O%vs4Iv_nS;`)=Ca5EUXxG1Qz z{!4@cp{~_ct>Cl++Cxqtl^bcn4XACnKH!9rzYli_Jpw~G)LPIMEXhlMhOdG^W0A{pHo|0$hMs1kLtzB>wYipWx!Nfo+EBUIDq_8H7mrN%?A0^t*;if3xY=eK=nc5 zfX0rQ2<|gsNqRgRggfDgaRK=om6pBLsldw4)+d9eS!foYy+buwC*S0r)sS^=gH)4v zHX5(C_V9m4DmJB`F;3tn%&8f*Zj=Jti`rn$F(qQ6H z(qvIlF4cs1XRCq?ICo&wE%ZnsiZ4%2BBgraNYBF`MrXy>GW2f#L&nwA%Z7igbvx44 z@4vR9DZf??bMg&Jixva(3hYZprmk40F6~Z&41v%;z(_$$kN6LB0NZTe> z^h6uTujc>Y`)4vu2}re3+hC~~0!-q`dA6=d0Z0r|^GSJB5zw{Ky>1*9uce*BYef_z z=Eycg07%|Y)5`Yg0cPydq9bL~5Do|`aTbAHo$_>sq7W(wW}fJwE>M%Z@qw~C(jI`% z5OGQ)L_&RDTI}%np>ag#HkYO5n$e5rd~hH`bIpJ>*8qv>-!jL1X{!yBclzu8EnCfb zsLhUfzttTQtp9oLR&&>=rr|FJfw_TyaNHskt-@IBn`@wICBXR05Pqit-ba+{w@$U5Jc;_Sx)r-p67XuZ? zVcvCYC~$7<>f(ZOn|Siv7?~p$1c7U}o6=vz#T+1Td$P}lSix8pcEGTnXhgDZ z@>5kW0@6xe*FA8r1E$CDHKk_k3h6b7<(a{w4EqB4wl%9H5W9dv?y1%EuB@>JW^XVT zW7V=$L;$$-RXgacl`I?9kZ@pIXvt?%w~j{qH`Sz_d{endyoBli^8iB(yF{f1hBU}M zs#?pE_PaqlqhHWKr|{|Y_=rP0{&E{a1%W_cg8r-12GEM-? z*gUp}(nj%;0~$lCU5WEc@rTu>cusF`#tSEfRP|zuo6hikkQdCnHJg&oXvYgw1Dkw9 zKrIr%@{fcEw1&^2fF6%(Y6<2#x$@2cpG4FS5VBTF*cV*} zi$Hwq)*-~NtN!%KHo~B+a|`S6mWr!V zGsyn-<H~ASQNfoe^7~_v^#){cGlRs6x$p00;CJ6#< zK|E=mDCJNa941)yd z)Vyz9BG& z3bVjfpf!Rf^h6Px*O&*zLtCUm{PnELYm8 z8q>FL6j?JH4Pid|BZuNkL%wpzc{{?w*Qee|Q`P{Z!yaiW3OaBfPciLofCXR@GC9GW zOdBiO0bM1}~wq0O;e&+XeM>x$cK4-L-U*ILLW)H;Zoz1L1T5sADJlc_` zS*DC__*Y=GrO>Yqm@zk6`kV-A02(u@Ri;VZ{9s%1C^C>^m(JObaUq*$O99($v_wNz z18UZ^s)Fb%{Q?JcXH-+0o2iv)2a6=mZ6CRY3IG>dYWV>=nNhHtj2YM^x434$V(D8G zu5uM|N;l3B)X&*-Yixmjp<;*95W zvq4N7s4&eOD^e_ei?qXJl2o8dh_MX#x+8Sxo@PQV14iN+`JczTzC@Ie zdxn>l7n8~bpt}}+Rke5&6@V}%I&lLJ0%>k;id?BkB}u2eSoie*qc?8JahA0qUje8! zREs!VQkw@e4tlM>p;QP3o-_q%30Mc_NvuJxs`%F+gH6E`gW)zi`i?2 zSYRN-;P|b@(4|8nV$KD~kfGO24^#c+^QNGsa%q))ws8OyFyfo~idt}em5`>qNZ5-s z=*MyEAOjGPP^YeJl*~i>7x4V1bH27ipg=t@-Y+Gs-_C~Uj|T}CMO=5NF3STE*S=7% zh!_Mxnew?Rm-fFtjVAYwaibjv{r}hKzEqTr1Ao32UQiUDbI7S z`=tOc7al$J@hF!&~y<||x0H1TX%Ls^lu9)xY7qKSDRdNVZ-(3F$uh%KSE zhIT@vHwlY;z6=zI5(jJu%a;~Guzj^sXE+PgM>Pt$rbYMnEXN?!-E0*-04ov^uYP9H z@jSE#xY+%bpalkq`UR&OCs5P_!2Ki3l=q&dnIjsJK*6i;m5{5p4fc}eG(gJLX za(P2tMO$bU1nS+>$wPUG7i$^ykj;2~pMQ-jb`#Z7Oi?RU%gUp>v_)$n(%Zx{pf-k< zLhx<*(R8>Cg;@=UXxfx?NGtu;9`;!G?=SvDigAEVk1$!D9O4byGzYxh z*tyc}ATp>q)v;l&?|73LQY?dD`k~m)24>4VseOjSINCNbh2^T2l|)~)LD1z5@4<%B z9O*Zp?{{7nDUNrq1>J@4wl!@t4SZ~3S5^juuKsX!DFX6P=L*jk7j^sriH?l!juu}9 z9kW~AMBe4UfF!rTv`7SNT@uzSW*}-Pyb5Nqz>|GJZjQM^orgiYkG9^E`rc`{=*Be_$CJrKfu% z=CmiJ{^F4r96Lv2Z=CsOsXI>VY2?J5=AHM;xPSiZk9AMI>HcxYz(0fcnfAxJi%;}k z*|uv#n2GnMx$e=QZDr2xUp=ypA?sc&AWZ?>};Q*`{Qpu3N^iX(bLz-i?3^o^JMD(T(M(O~e zgyS0dc?X#P`&)IH@<|Uz%17-WoOAvp-Ua^-v<=B~{KCdk5e5zrh{_ACt%?u_ZDkom zgg2C$4qG8q7+}n;0u+G}ZXc`ht3yzA5E*_8>g?ES*<}aG0T&)7F1jt~N(p51{PL-K z<9d+&*>GnlsI%P8LtYQDYxX;$%e*1?yT11AMuKeVT9BL>wBHSOhe!kOaW+NbfiXau zkzv{O7;;tU`T1o^Uv)Uqi4z7EcUz{nA}$OBx}3P>EYJ4g=D0_69)@)e1*yV*KSd+@ z^q#l85~8^1!RYW#NcsS+9d9YUDpf85h-}Me^40Aif6r5AT;X2a{WDDfX;~dH>*-LJ zm1Wlu$S!(4s~Jf^MPUGhovWAz93Du>K3(7Ci*UvOKr25o4)4EDgib3U9s^%PK6<7l z><}h00l?0`YH95cx>e!7A1nJ2O@~n-D2@R-tMKo~N{BRs0f6MHEovU>AJrN52dUXc z>`kX;nWZ&cjVeO{k`U1tD$XD8U(sWpkU(sIm^1`RdT+XtDn<~2 z6o9eXk?Z$I9)l!odTS3&*#}XydZI%XtYgGNYgSiP)UnYt5$*t@fvu%AsMMM_&7)4e zNYMn@*Ha3RhWbP)urjkGAjpiJBfSPvh_d)Vd>2R|W;`EhML8fLW9_0av<0P*6U3-M zue*V7+6W;-f3jKC1Z)`M_>1$;DH{WKV)`Hrt|010c0=kaJQ-mQ7zGBit<{trqS;6R zo!e76tU89LJOkp0+;x8AUx2pYkPY38N3SZ00mUH3&B)ZElo0JcKz$Wc!<@QmXw+Va z!s*>**TT~FjC%&L>M(sjc{+p?MxW^n*F$^;=4eoxtOylB(!C?5HO4l6sng8?W&IdS zaF>#D=~0lnRZ8Pv!!eoFCc@ZKeII!;hztSkcgIIG?txU!mhq-km;D%`a_bsnWw4=M zSp#V(7ew7|OKS#bml){{ay#@4*N52MCkGAd^FTpqW*;6{aa~G>@XOhAq%#~;0nJl- zCykc8{^dxa;vs~vD`OS46Db<9M-bs1NylBrOpx?p$bM;+rIiWW+fw^)q52?}UEi-g z1S*DU6craq{zRxSOmpscV34aHo=qNCOyrKka3%T_^C8Bu^(C7rTSyK00pn~25VlgN z+g8KarA>hl!t--1s;or1=^a9*_^rOY+{+$)Z-L#?-fGJeAM^5ix3ARdW}!blcHa8I z_GKq#UVD>EeQeH;ss0(CFZpTL+Auj<^wqaJFURznf5(h-oU`@f^7?)$z0LDGq6LWB z9P^P8jSoLOX}mUUsE5@&E~^|9{WY0{SJ~=l&IX;^S}QAB@p%xX%eVL2=tN9gqB88s z$G~`=#H)TUOQ-$;+1pTB zvB{!u<2X(bWb1nP0Xn&*_lppFDX%gxt zIBJ6#f>`EraUdL&p0lnrQcRQ;zXGV8T0z0-*L28fMd&ksn7B`Y0c}d%EI9)?Rd_D4 zQ5tE4xM7+w5)gNzptt~9;euOZ(?LLU+n?AqtYU%s+466#a-EPq?L3m_C653}M7fX) zJGG%j>>d;yf;8!gh~2F{M29HEtnpB+XA?jWDz}SW$DV-%;j;92aVp{BR6urOpA?M{ z*8}8(g?NS86zU6H3MuQ$kDDz$a%(`F0`havE8}!d2gKBDpv@Zwo!o^Fi%rUlq@4=^ zgmvzW{|Z*^Yk&81L^KEhskSmG&L32_WPe*P^Jx2TQN;Or07BVH&c($7P%0!si}{Ke z_9zBHRT_o=Q06yB25L~m|8|wOpc(H0LH_U}lF9|$0O|^Z%Jz)(h#xiiByI{EMvB3)yT_7f}{o?c>oCIofp|V)4~E2Yn5$#e`3gS7ItI+M>?%$KO!93Vnp^c(3Ce|`TIJfZ z5!A?H;D}Q?QIyaRAwd6+8nRRFfPSr`*^(HSu@4au5bR*ny`K2mb`a>22j`0m6?Y)C z&5xRE2Fth|@NFEUD_bB6(K219ngR+&hdpy`F+uSMAayWZ{<_y2EH?R2XTm z-D*G}wnn@rtoMnNAoLhE9pWpfjo)aS&)A1gIDyn5JBUT}HspZRcUu1G2~fb4=@~2X zkUN1aqgs5@BG7iJcZW*pFb*@rdW<0&G5v;lg(t+fiOG&3x-E-9KxZ)xQbEU(G*@Z} z4RPSJ5r{TC*+TeiiM7;KfK=O&?-z=L86vy@BD-76V!omtfJApq0{4pWJP=H`Jma9j zc{;<1j{pOro$iW@qwuxOMT#5hJ}i35DiW|T+J#1K@F~AmPsvOGzjv+CZ4(1T0krr z|Fy}45Ro_(D{g=={?#&l2vTP!Jlwpk;+m8S!gxKAO<0;sUgOaEBvRagiXh`^)lhTI zP8-NUsa6EgZ-R)y zy1bHsaCeLX60m~3dCRLtKqDO=^IS>_q@REk_mZ*D4%8M=II~X@w`5uU<}B%S5afBf zEH)^Y9qXtsr70WU;WbXliqRtcT%Fs$D+PWy1fNV`ZjhHk16b}K}$;jvPhn*k>n3jsyQWk~WH7%76EqE8I|9%%S zYoE3NlDSK}eT7Px;eoO#k z+AXbMJ$(aE;9U3vkq_*Ph`Ir!RePo1o>UAuSb44(KZa$2kWIM?%xziaK=hgw;YT3i zrFNvZdN1KglTVfN4C#w1E0sQih?ZLYO-fG~WDnwhVS>hPN-S0+LFAzFfYhQVF|l?T zWDJup(OWqIRX+;hmr<5qk{=}gx~CL3p!mr3AleWi*`qBe1|+t4Jvq;LZanJPjMG6p z>$!e^kPg|~n6IR9!A0x@5N$scM{R~Am-0^f;IF++U&r(mDdJ&}SJc=#?bqf&G7gNB ziXu~qnMN?k+r-*b$m|l{G_g5m0aCMr{A5aQ12H!h(&2Q#+s-#NiEts8g9O5AkG4Mu zEQ8h0&X*>GbZyjJQ+5gx7a6cpFJxZ;aV$+-oYfp(OVtN7DH|;^tLZW1Lm~IM_S^zt z>taU4U$=o2B5bhsX%>Sh$~gkrC|iF%c57a@E=^GT4sU1DYB)14E*3$$$MUR*!~+wA z(lzp_a5dTJe z*0c@RfnnP7WOR@Yk*$%Tl3MYr*NsGqq6SD7uJ5Z3zXFM5#S}@(5y~Hvg87a`KELRk ztwV5}08-e{+TWd5q=*8U8zIpt17gGY_iQ+;s_L(zLjmtVbMlpAXuD(j-bm1NP@YY0 zOj(Z95G12WT8WU}> zJ8*T7w&~_+^?CR4$R8!T7(p7CA^{w%S{#6l8NLJB99rfV<6%;fAd?0$@JV8jh9scnO7X7<MRBqQRY$C1NLUD`Lp3GezNzGF z{9*WYPhub&k__z`-C92STEYZ&pStildcURn*i1K6UD9h6Gw# z4LJo;0Z4w0V%eSM3|Aa7oO@4Inj@FEQ4@jaoI;FI%!H`i;n>D3I;RwJ`i&?Ec<_|5 zx%8|o0FWKA=j5WY@Xi*ZcXInwGOAiv9{AR*A`2oId6*kJ3}b*4B;cH7mq{CW7LsSf zd`Uf;?xza7sn1_-9x*7-gkZ|W<-gQEqH{_>)RpYhrj|O9=aU{Q4<}MB%x#<7SvXh+X++mewN3S+ez%pRc?VvIVOh z+eMUYLbkMHcl0uUp6>rhldlMbY!qsna?c)xWKj+{R*fi73XHCrxxRJ!gW4R(Xd3>s z_D1{{kUi+nSYvAlLDtEcGi2D~z*qQ#Xi%DaR<;FT4TQw7KpRBRHdT>Stt%HfQo0Qx z=k!!Nr!KogAvLd$6e;{b_=fRjf*OGeHx_RcT$B2Pw5-L<+k(qDpiL3oB2TzDLN~My zE@bm?q-*nOTCn@c ztQ@W^3=y@e5`n4DE8kxp$AC~a%MZJZGJL20R@K`1J!U5}-`w;zz}_l~^?tY}-3yWg z$FGU@@DRy0$qrP62n-mlcos;1SDu&Zn~uKFkU%B|@{~;v(CIV*AGGg$*c2=4fVc`A zLspK|3`PfMolMZ3U&-tHIW%Pf3{m^Y%?APbF{8ol=hHpq^&pW=*g~yKhurp!7o`Vv zqB@A+xhkQW`n*!3*Rb2_(RnT)p3Rdx7}^f`$m?JT?=ZBcK=#P-9QK<;2zQX7vASW- zK2l10^UGs3rW4a|&!Fi(Q@>W#n&>swQUY<62DrEZF;goJrVF>(wQT@TTzJ!vBamvr z1oj6knXXUFGHfF^;hsTstxRva;5JBVzkIT-1Ja+aSp|7XOK4N=AI3Kl)SDAW_3MAM9r$?f9mtfGn>j@x5Yd%QbNwv ztGp#<(<=go4?v-t?usnC1vr$fI>PEIrBm}jvdiLWW%@%zLB~6(LWl=}*w()mi;H@g zq=aM-S~YfJfq&dY^Sq~$Qq|g-@~uHTq{b_O=#1Qf2g1o8j)d3YWY5}I4Km~yvw z2g?$xS+5Gosn7Gd@qugxz*wggl~Iirz?G69iwm?v2R zLDry;+wD!K=0cQA%{KdN2nQeoUhPaPEEptLm7a)tV<7<3Y$vC#+h)a-Fk2|(dGQ8A z5Y3I9KU{(~c&TLklot^NcZv+k`7sk_04lUfh$aILAk7W1#cR2Mn*yH6K!d!Sc}1Et ze+pJAmoHP8o!s<8kzxw~fjznNd+hj%eIUh6Ni19}f?T4G@+b>ThugCX*+9U>8j?gn z3`FP`JYj=~npA%K)EAmOt)qy!A_(9$wr9t@@R45!nVOb4ENf5_X6cjF@|#rS@*C2h zL40$zU=sejPInZ<4XH;TOk72&f0y4k-8ahm-gMufn16*X<$ZQ_=OyMZwFPN~$Z4Z5 z=B^iO6Om-cS=t$^c{`)$%wdiZtxAeVf3fzw;JV{8W{}42rfiS%DdlYMU`z8aw9K8C zZnM*W_I2*HBc=KcY|)tH$05P%e;O7(dmqt!2XM0Foy_4*_+jcWJ8^Y++?XDDUYGHe z$j#63tuU=yPTYJ#Y>%gsXop-O<$##?a+kDo0+QLr=T?_-I?qkl_mroB<)e{H#S- z_2#GedKlAqme$P>M{hz=CL+YdOf>)jxZ)|ZYHv7uo7ZS+?fP$9K4 zX=Q#XA{5sLQQ^rxO;GZcF-M+6T$)@5ZSUt!X*6;nwpZ!RzM&=AhNKJsi{YyNsQE5B z90SO#65)!MARHi2OHB!6E}2-6Tp_}jfT$YKPuzYEYsj(yx1Y+v`vId;gbKuVk7gre zOodQ@yJ8%e(%zPK?+w`v8?cDAGe>Xkm6f4N4^>>6`aHr7@o5+WRKR+sVo^k!8^P15 zzgD@nx*7wj&-89>M>s%`*F-PGkCGtmC-{dRb2bKs4$_nvAazKvoHq6P~Ff?^vJ&g5(E3{ z{lz8$2Pl$zfW6HAW}sp#rU_Hvhbusn(|G%Da@&bXu3JDNkjv9(R0kMwT6Fcq9!$A* zDpypkD<8JD)EffMC;a}PxJDv7T81baQ~~?1=eh{!Vv^r9E5r;N0wTNV_C`j>Hd8=& zMfk%8;$BiV^S^LX@kjoKhbVI|z)7-dVT?act&k4QQvvC}9y=o!4LA@~_pI+)g-~H+ z^*-X31R1d-)R4yfT$~%AgyV?OZ@wHb;rnCkmvk}XBtHpg9vM~V1jG7)y#G~pU z(yG;8qeuz>+roPP+J3_Zr&e49lCa7UpBJxR5(jD!>_HYHdaOdK{`>LfQ+{=jeZ1+W zX}5nNdjm)c0)8YiO8`w4#I;(I*J~#Hu1WJ?Oq;PUax+ACcQt@RuJ3L3K{I z>|z0QZ1Jdv;b0k0ng}B<>)YFs1c28uJI61DisHY)l;J(GTL<7)R{U8>o-_uA=rsQA z@~~~u;eax7z1l^TtBa zMfZO>arz9eHzR-Aw$CoKY%@2%z--)_vHZz%qS-ULSlfV~Jo~~2PG8w;8aQ7!T1&*B ztrI?QV$Rk+pSD5!quIutb^Y?PAvTrrmqLp%<-hnp%ej^Nt0o?T$}*WWGt(A43np~$ zujmYijJs&K#g#2Y7*lETKNwE=b7hi-tkRaWkDvHbb=ftk8B8obY^sTdbZ;18F3X!| zTb2oiOI&=O1MET%jR?D*3M6VNe(UGFA#KQ{03^UoaYbqfBZAJZ>WO{ihcwUq$>|5V z1P#^#!S1dO&nNfy`6pr}i8R3lkok4g#;TZr{z0CFeXh8)(Bhn+OsWe+cW>loz?qVE zHS%+!1b!*Vr04&HZpbuDsHF~()}IM2M^aJF_fzCu^)kVc5&{@M;3*nQWb5X6$hU(G znR9ed=hMWCF8F?cEKm%s(|zUJV1Ox(TElYk?T5!|YoIC5g9M?)!m@4*aHTQoLYHi! z3!p$t44N&w1i_?tW}ehS)?Mjzm}IeTaK(UcE9PuY7HJYkk~pdmRDyx z#Mcg_lJ4_bWq>KF`~^gpm|}$%j0nFH^HYzJtHLi@J%WVNZo@)0 z7_PNHASZX#F(T2|TfQD7vJo-3ODmwm{wl9VGlWz9R)F5IdUa8{i4%MvS_bG$#(R*X zl@(m5mmUAE+++19$pUbk4(nJly+Za%m!>=eGBPi7<6SBM zgTVO3QZx1qX%-CQL-~HSkfi^xXG}Yc3S(Ky&(an>_c|qDBR^Iy$n#AryD3eFp<91o z-tZ&97S@h%%dSfkfj}R{Ckx7XGi8nd7pWPSZ7Zy}0ftSdv_VoD2W&BVTXV5u4$RVg zxL2EVcQ0w(p=L>lhkP;2)Xuoc6_=17ZiF8Ws85e{^M}K|bygM&f$LS5OjkyMbivu- zAggOEZ_v#YU~P;WA}0c|vDI&N{~fFTn`^os0|ZQxLr@D9xx53^+gB|?I+^K!qLTXC zfeJ571jIC2iiC%eORRXoe92R@WG+L98-m;pwXn>ydjYz8$dT0*Mdw7qIA(|V)+GkV zaR8|lsqUQrgg~t~ca{yx^jm@e?WGDlOte3CR zYe3zWP%`>xtUXvn+!CAuk@w0y4M{Oz(`k5mxS~{Q2eXTFzcchBDkPP+$qh||-+a2@ zClIR6S4ZL)fNW&Bz;rlTx{1p`@g*>h&A6_`t;oGU0=U!=iz8(}Ba~m?>gYRG`R7Xl z#{rXgcv+!Wq?>@WrA1G4kRI@O`}0J`(r#4b{0{WqzH;%GY1=vrCVdc*lJ3bmQse#vix;^wpPJnHRp+UcRaQCu@)TTv2Q1(jU!t5REtb zzkhOSY2&F=wr`@&wqK=QiP>?sS3K*}UstCF4uy0Cm#3?TD`WNF%GE!u@}CluPyxkD z2?r@5HIMxBxc>4FVHAl)Daq+ht_pYZy=~Q9;(Yo(%w^JM8VCUFOK_xiMWg!8|Mbm& zd**miLw0gMS@4?YzP?=)OQal9z)-^geHwT=&t^4Fr%@wdx)xVb+YZp-9mR!g<>VmA zzkTtP2bHb^{ez5s+RFT!WJfc;IH+^t3_A3VON%Lh9r=30fH)QZH>ug7He5Di%1>1< zV}c*7D&BmHTXv0Hg*WgU+wYEzpbap5TRwFU4h4+x@O35nIzPvyFofyXW6JIUaLs1d z(P>mRObD=%n#{zD21-CkT!)b%x3&G5MiR*x(&I``hLgiT1FGFUO_WQ%Up^1k6;UH4*G21$#r}H3?xLgg{06AqdXNB;k1)BeW*WVu< z|A5tBev@2c=NAS4wmMyeKZj}9=1<-0^vTbWdt&gUTVC=$*yk_0ZsmV>Y@<_`KY2J% zoU);-nZ^kP>2Ac@edl@rg(E4z8iQaJVCc_jW3>E`J*y!u_<9(mFL&oLuruo*Y7b{+Y zd=Rzixmu9XzQ%VmhYA%#Fql!i`xEixX2$q5-Y$NIh)Vy?^?!I$xma9H+(c6x_mfS} z4n51dDh&qG^?Rc5k~~F{SAcL+^fm*#~0_ViX%NTSoqZKO{B=BKhp$Yz;40Rf|>UiFsYRTUv=^vg{UKU4+ z0i%NDSw#2ZVnq*-0vn`nM+ydC4Ea+?fsLni3oG0DjS(ykbQc z3{Ned=rPe4o0>;vVR%TNf4h+rivJ8_l~2-|dJ1_%M!pGAM=eGvU6OW}^u? zN=8)K7XKZwe|S zkS_3p3Bjs8)#2oAg?O>p(55(I-qs5k4d|I?CBXn2yQ_2LIm)3EUraR1XctVd8{S($ zoh$J0)Bbb1_YUE2t;bj$A}lHH=L@+zFMNN1J;%ykBgGV-I+_T zdrthCv70t%8|9SV++$qB@>(_j&*`s6ZRQ-SKUQBKt~qp`ryQXcINt1rnUx z@0fv}A1h)5fo}dTS`4tZb8if!HuDwDFibapXV`yezcwY!f$StUTO9YSqUvEL>@t0_+o=1Ppz z`FSo#a9>`CPZ}gIg5&$kZ;v#_(3KuAT=fo!B_5>5MxH!7hRCjqfuZW$P=~+%oZNPg z79Fq_`O2@r1fgYb0kYTOVlCq_Sz~2hxzAW6st(i!ljaA2x$eAjqRp0 z)DVa@v@#j>MVY!Z&Jh^5Ms1=~waBH_iRRSbA;dSr^dO&@odw|5BLB{Igi!zzR<-G_ zlKX=*th!E;6X6dmGBvS~{TQ|>EJ)HD2BbJ>IyCnX{x=j`%(2{Ot_(0nMe{{NiOZNL zq6vk3YorsxFvs@Q9ZmQu7{i`eYlb+G>#T)8+M`LMy2C{4a_iZ(4vC8aH;&>mdA8QE zJTLi0m@;_W8Uf7d5#CFlB~zgK2S5TAaVp@AGkKlDAwM{XmGd))78)~qKS3CGK+x4@ zTpQVBNbb|(q7C@#YE&JH^Ilini=1y9!%;+Z8;Gdq+Tjx7!ZCy_PSv}g|2*Nhv)Ukmh!v~zY$Zv%Z|3*Wa(iJAU3E$1jEGFj`)>=J! zDY2OS0A^`c#%hE#l#vz8VCc$WH-rLYmzBGui>O~dK;qDPmS47Yk~}Z4^Ov6nvdh@L z){ea04JKSUyb2NJgV?2JC2s9Ja;x8U!k1KN`NPuAL>k8h$S~UT`e1T~>2_2l-#!|I zEn5k~w^FFf0vy5+A2VX)?+lk9o33Da4A*qAPiU6y^3 zY;}hh!R{Se0W$*2w&9|kGV7Nxydya~aB(h(S-(k+pCxXg%Va$5P zC}}CbPEGoi0fB@s{a{M_a)IU9Jo3z@xmSq-GdhP3 zQ?KJ+ICa^RN>|DvQ~6=9wupSzRbg*> z|BJSq!|n?!13&)N)^PEj9Z$A>WA9jZsG;>|!^PW|oxZ_4{jqWI=Pt~W=wh!t@51VpCvNQcDeX|%Y2Id{3&vm%YfKUTa@;?> zG)aKsjX=Z?^tvf_l2VzU7aOANUZtKY!kI%+^mJkfsB5?dX#S%%kM! zQlxRkMT$ExO$#`Ocgk+AsppVqjU>Dfl^Ph*I0sNV9@FpUB=08+=OfTs$~NRH5X=d`mbt8f=NRuyz*R% zWFMyd76vrTXYDadKvOARxu?R+u=^2B=>X%Ip_z;*y6?py^Odi5AD?*)3upq7mIRM0pd&tp0)}hG`Y~F=A^{Lo+h76p_+_ zwrl&tBtU9IT7`_FmDPPq>Hq{AN%Yns&)7EE&+0$ej%UHF_ZBxTvkLM)3!%bDv8JX| zdQYJu8z`{vKU}?yoUF<|x6mUP7ibG~T|4iG?~a!JPI}xIe=;k?4U2-p&4d(E3OUkO zPg_DlFb)*#jhV7Wa#UYePkfL0Lt%QkwXiKn3A>Nz)`T|fgXw|&!RRvg-6R#&%)6Ld zj}+l#!(is#^?PxTPmJ3jg}Xybav9A7qKcYRLV`%Yem29Yi#gIND!UmBW*BA&lmYLXL<%8U-#6ng%m-1v!ncsRLXbCy&c%CF*(i613a)>)(hALf>} z%eQ>w-@%-&ml3_TejM^)YSZr|)2O>(q&0H3tfP{qM+kq(=1z2WqxBCEWfn}y7OZx! zeMC-xfIe;NGo1A)2x#SdL}?v)S9Y4RWHILj9s&h=Y-C>r&s^b0CSCkN8=}9$W+<~T zSA{vLIY5i`i1AheTOhZVT6U$9wQ=RG_o?l-O9=_Y8diYFsupGb{IZC&_Ka89UxiSh z>|X;pIHPZpbBox?uzfq=V_!d$fWYYR?@2lJQLM~k-Xda9(gq;6&VSoELEb+fVLNqc z(y2B;Zq=Q8c0?LSCG|OlmSZ`7W#b`8VkV4X3x04?*e(C)%i36a4bO+JbK#04*)K<-ISL9~W0bjgl*;+kz#q30w zgjFh!{~0}gh^P%B?*@UG2Nx+4fN+2ATOuM&?c$9FxmDbj7JLEYSK6Lv*(j+CSC~WT z!Ds?Yn8?2L^xb~QURzm6wuO5=Sv|1e-tQx)5XN;Fy06Y&B-*Hu8pKfnyG7xFSpZGf zW;YAJhcilQ$0;zeynm-2k&-@vR)#L+!Oe;_i_(%39( zhL?OP6lPI~S49SCjMi!>&4GjcP84li0LtvGiw6GWp22WmRCq9^31nA)Yh34;fFC2i zN-DUkuE`Qm${v_-wX7vhmZX{WJd+_itt|HV8B`q#cdt8O)-FFPHN~_IHvw9%Fh94X z4$?#ho{3j&DUD+RlUQd@%PzyYWLHb2d0ZJ$FntY1)cker$Gb1QWA26GaeZoeYf!>_ z3yztWoyrpzI&+%LBKTc;{_~yWn@zyi@ zZdT9O9ix5XkF&cL+*toxX!FilU(Wg9$Y;AJUYytw?OPX6FB|RX33)l@Ke2S@g@65D z=)SI-NC$_a-ig8#9FNaa2|E^#7OX5zBiEtsNHTD%RCM%e=o~0HzhT@#&%U=I$dcFMEKrdiHS^i0fgBSquL87&?pbFY%vVG~VTz+B$lLNM+KTBMGZ;ag zS4b@=q4v=!nq)>SWk=;N zOLtFFFH)QYQud)(n>>olcOXIrE%*$eY1Pf)$~KXDa^4~%rOz8fA^jYvtoHWL>yk<1 z8>j#wDj6%8$GOTf=9UZa*fmL&~ayMd;W}OI2nqnIt z(K%G0$?}b_ZCFK8eJZ@!tR9jp9r_%GDB8N+CtgEVOg6ZR_Iv%YPEa#E|cbG%bpQ%{VRssReuU)(kIBGU6G zt%z{+>B>zoEr_tV$F5BzwOOkiBx7m2u+QOek6v^+Ln^QCo^D4mq5wM;M%PNW-s*6d z=!wTCvK8b^_y9;XDAm6Q~P4Aoj#e#_G>1z8lwW`KVVKtAl3>P%G_F5En9+!QedB7 zsnffJJe`ZL8!MA&ol9jM#`K~1elk1dc$ZjbxQ6T$%Wss%<@w9IUyGhPL>hCV0Pq&z1Y*pqtlzWzyrUhYe=dPqw8Xk z^kf*s%Guuzp?Of4noI-E9kR_!EWAu) zGu{SDR{sTgjpUxra6g&dc4C?>@{+dzCF|I3O-UQ+fkQ7-FOyru_6C~mSr=>l^~lWO zhwHoaH=Ii$#sN40HyMvBKJ|v=ta6lS(WvWz8W34h`+m(e!bXbj4U% z3rMr3LsJxcBFk=pAv?bBo-#!0yoBbKB#GMbt3bh888e11BkmJPFaAQAN`?I*+@58L zo%#l*wfW``SC^9x3>K#xdXA?;(Y>k>GV~8RFrt5KaNt?NotZEHvRN8wt{LBD^OMl! zN>#t~mdVUH%=1f66Dh?X-@kEM@524$(+}?_Z~10^>VbyKmll*opSJAN&evX2V$iQ< za4yWmoBO1adF5m6nM*>)b=r4aKH4f#%>GxKSR($&ES{bwPuJi0S2}*QoMvp+Um#tW~$*Vi7s8R3P{sgl2MDk z7|X6}WJfUjYo?iPG!7WwcfqfyVl-YLV~K;$-_sHpyx-92!xbyN)tW+ zqF-6oSz@Z6fWL$qIMlfmCdq*~)<;g-1V$T}r`nyKT2iPtu&M(QJE5)DDq_Nma}1zm zI1(%*NCX^vb&$~(s@(f-Gz=&&9avtzI*(mHi^L~^tibkFP9d` za!c~<%e}#fv$CzY`$`f-w0`mZt?o-;<<1bQwxGf^qj9Yl#EtOWm)h_)(AspF?em7E zvl0iWuH`YiDF<|Gq1RiM%bN^75Z5ZWNjl}-Uj8Hr^u-cslz0AVRGTsfs%~#2HDEx` zB8;`sx*eJg-#{JiN4S0j=uU`zR*+-mo}`-Nm>q`ZS{M56Z5U94<;!I-;GD7XCNz<& z^8L(*d%(iXB+XnHoTqhzYFIq0zw-S%myUpyduVI3HC>5|{gOb!Hi$@|I$$AUgR0<2 zeHQTEI2xi6oMQ9O?XBOj9}e0lYX887g7<=G2J=+7Crse8gWub- zUg7-L!osO$AKwkv*nE-2wmyDN&%Sn{hi-hgOa{=ODs&}T}AG~8@W0|$8s zn$QcjGP`Z0i~#dA&=(r+_?tE34?|S!7sFjipba4Dj}3#_!{2cbPzPIncv=mXa>5Au zy#T$l8RBX^&Cr7KiyY zG?^FfH#<**UTMAECQ5$=s#4Rph`>r6WnS?y-9Y=(Ca8-a>LOBZSx|!@%TVMVn|S?S zp$^mn*AH4lZ2C?rGmfF_WAvAxirv0NL;~9anhq&DmR@s^P**8$80bR;Gt#kw%%ynA zHmKIryCc^Oj5W7>hM;T)dY`_82vapR@$UN0_lJ>SV-1va&JsBekmt|MQHVihPT`wP zEMg$xQ;2qTa^mrRRK0yJGZ>^3j?(~amVpSUaybWGj@kUfy_Z@SH}%3jZouku?zg|U zba=WZe(uBf{&e_eiMapU>W`mz-JkpFz^%jP-+$Eb*ERKm|Msa$+3Z}XaJxBnfxGFi zpDJ6f-@fv}V~>XaJu@LJJYYxr=CFsypSR~YY}$0cg?dz!=J3yi@iKXA%Pl~n^?0bp z>#8phg2jeWKcvSxEu50bH$%giN4?uSsM+sp*dQ0aMQ!<{!n+9%hiq&3Za1(1CQ#d% zaaSpM586mi-H+RVQ2gZB&jL%4nnvQJ{O4NbAE60;xNvxSHOQSwWm{zDC0$T;I`;W) z0oYlbp~lgt^Gl+h_7^3VM1j%15%vhA|mKNROA~ zKpw2^_%P@_Z+7uybu)f1#I<^56Ed9wmKbFLt+oRk`9Wy5zsk_n`P!*|d$aAh3jZrJm5E<^-) zH2itTya0^YZ$_)7wa9FUigmyEz9t(4R3G?-08ftQKwT^QM+fJF{liogurO0aPtbZ1 z*o#B|f$VQA7C_tWbs^?$z+1FV5bgEcd+!qW>PzewKyR#2)$?$*%&QPp8~iUe(I3CT97mX%-mWF8A<0wJ^7jX*Ur45|w_#vTYohNXj@odbxn`nCkMrWOfV1?7&% zkYH)d?BUUDGSCyd3*ve`T>Y-_rh{w~3QP^sWc_tvv>pjj4X4cV62$vKg(eVWodPZe z?*MhUUA=qtph!TkvQZ4zlCq%M)?1#Lv$ddOJv6nbczgA`?$t^R#BK3{ZP{AQD45mS zQ#N8(vMcIX)@rX z;}wWH{o+}+TfNoCyxuEX=&dwHFyC%&oIoBc~4>xJike+;b zX$C|Oi^jRl0ITBzQMISuN(yIy9Y86oh%LZXC=pOS>kr4nR8la{B90*Ugo$Y!`2mOv z_R-fC3+FD+%E7QePuYza*A_z!HU4X^3;bmhecMJt!V4hOQ940ZLQ|SQy#4?j>>Une zF0g?efx2AZB?q}tz|sRwd%_hC394Ri{{^^q4aBNAEuO+%B?J$NCsa2$a(C_UI*_F* zbB=qiIV(XxmRXiH%L1{hp)utY2MKxZ{h}U>!}i=^u=!WuZGpqRSx}AYeIPRetMNvK z6ag3BnnAUvEhje-_O1M;3()3~<&M(}Dv-~#KR_O;gpvySmPz327RB4Oh`&N~)tU?T z3t+uTQ4zp7S>vCfNxbl3L&Czm?U8;1*ny@%s$mRGLX2n$P!;e$+r#ngAo)vESoW~A zmU#)V11!u*(hX z8iha^ty{_=?T0wZll;i($Bv>YF!$dQ*DX{Bk^P$u8=yuk@AU2xgo2!D3uMs>;$!qK zP%+i3U0aut;xb(i7Gd@I*=TNZ&`{ODIchVE1zW5moa>%Jgmq9?YyXyo36Lb#&NJjF za8Hc|RF#(R`8jrbuCZX{E9Hmoxws8yyE#PbouKM9J1;?-0YZ;NHAq@g;-mDtp$Z+a z+Lvr7vR_F0O|aG+po)PvCsv=^aPQ@&rax`pIaaXo%T9MOHuBaV-zl|>`>es^fNxmV zr?=vpa?1k`9O(Gs1O|>O-wOwW9h69=(cp$OC@p0{L?-u-r5Iy4&d2TZT?9!HK-cnb5e{~Q6@@QT2 zPScnm`^`u>&;uA`{1a3Sd>^|`a}8|vS>>b_{CcR9_t?JM49uwLaWQ*w;SvX^mKuBu zP}PxXDoCG}GyoZ;{|q7`TsQ)p90qH984CS^G?D5L9A)r?>Z=$pu8eZaLA}KWl63@Ho|?bskO$75Z&~lE$#J)<&kM5 zh~%cebs8Q={v|}Q&;5Y*2(TyT$DU@+y41TBImGL&pxQu4a>t}6GH{bk=*qRpA=yaR zB9X&}I^foww5eeoSX|KdtUws6{{-q_`>Jxy#;isYsYm*rq_d% z{b^4+fp)5Jh;jES)d81jbFdgD|Jh!lP-_R(v#^my?lJnk1H?d(#d{y{sF2h z0PiCOff#Jkd)aBN$`eqhU-bEIJ=j~Ki~Qu=;qNx*v^Offq08NG4R)q0i zMFM?&U>AC(x0LGw5&sQQpLeNmZ&NsLSug`JseNeUOYc~9tQUwr#t31+&DqHBAj*C?0+bEGjtTCe+AmDi+CY}M z^zNwn;(h~IHF8fweD~@=`CCvG8*%G2jS7OSd&skKwZaQfXMg^4N5L*gEz3UJKhMOg{Qd*cH+rvw9Yv4YPit1Lfr=_8ImzmhwtHDiV3!Hi zVsQOJwEiuqj+MvtWPnA%oR;0jfJ`S{5MmWn3$VLYAOpF0wk)|OYbjWwwt;8@HQ4|R zYTs8dL0mR!KpO%iLya6c0;1$^ZGp3Pz94Ld#Meb+S4$#5l=(14Wi?>i26{Ux=SQHg zSfEv}hA20t8nGLcCX9K8q$TLaIQ5V<5EIc4c6x1S2RB=>Ef8UVIMP$nxOED!PLHOa z^fgMM8AOqL4uTisYr_NuXtZASHZ++RvIkA|r`#NnuEN^m`n`l^B^UBgkJ$`M!L|X% zb;B%1cm?pn>muag`ex2P21$nkf6$)RwQwAuuKx3H1+5Ahn9%R=&$Uxf&D65~06u)> zlTcBYv0;+)p+fB>bfK0Mpi~G0J5$j2t&PGw(5QoE0jn-lEQaIDLAI;g4=>fXiTNMa z3ULs{K05KPp`G#YfFT`34~4)zzGw;B2BO?o2%uja$=0!Qy^ihmt}=_vtbe9&a#UAqos%qa?YN`ino|8w6R?zaKCiJ}aK?hmhJWFNqf@>gs_Cs~f)8as*8luPxf0*4&0DI>- zt1fGnOL0H1`k_{cg(&#Z_lrT|2{5c(gMJPl)3!l{@SWe9r-2puK6(y3>Ir$2_Rr71 zn9q*r7lL5I(0^%9F!O*rwZPgODTiX1gDmppxoOGaX%a~Db6x93 zk}QWEI-#wGYEttJjz`rXtr$ajoTo`ON^hu$c=m;}TC6zIj|Z#B=2vGeg-Y_RA+FZ5 zZ*0h5b>mq_ef3&$P#|FuqRRF;LDchH3&kb1w?d&uQ%!C?Ikdh1za( zGhPJMYL5WVJYEEoNiqy&qvE6WeNchrwy%W(to3&;v%O8Z4XV9<8+K_A1?&K0ldu>+ zq0NP=)S3(6m%6CXg1z)or48h9zU6{Ar@Fsop=agdd6RMO67YmjCsd7|{Mts73zER- z>J)^H44I%Jz=MQZT6%@%_N9wUbaS%J${6>u8WWJ(|2fLFF{zq06j!5(^S% zHW{qO_>5ZRYRD^fT2KHSG9!SrBS?a5hJk09UO`mp1?OkrMZ%czsnyJd5oN706{6Tj zKz#t)v~mVFq#ZTW7&t;Vh%!%QCr!AG_77^fWdZX8^b z$zehs=(^;o!s5fzPKv6(zj^H+e;uU+E-xR|CPQ}r27Cz|cPz=vx8 zG4tIx^U!ZLd9$m3zm@zxfG-=33zzWSf)FyOy2afcFPQ*2To&bFS$i{?0 z2zmYwHY3|!{rrAfeHs z*TFVdymELwlsNKqk!yI9^zc6ry(#1<*t^9|pRF5))gmElAb-Tu08-99uE4)0o~R&l zwI?7i)}aVY3P{9S))^OoA$tTXO*Z#mc*D3dO$Mfr5Vqb+zWZ55_VZo-D?K>;fT@*S{dk zmr#xMVR*!I21xgbsY_;_#gV&0)WC%wUUqiX+2&S&puqQ*ipLxwbg4Un$fY35|IyeF zcucyVM7#mwSRehAcX}U43sThX& zyxoWsE0+hOwt2Br!huGK0?32)6TEqzyk%i=ftewnjpKpddDI!6kefbE6iSg+72GO^z#@W76#$D|MN|2qu=}B1JQ-7!ym_PVd;X|{hvM_ z*%}=2{c_0k?T$utWk@_=0u?F8`~e9(MTT?_iZ$b!@~H{mg?Ux z&+i95+nk**+nI)updsVT;lg#AW{|-eV_GP05+z9JLa*FFgq~bc0ezVnHOg&J*XdM* zV1jx6i^D|Zic+JBC5H`@qG5;j>iI&|6Z5BTfL!=)bf{@39T9A{p+K9 z(+?t6w5ygz`s)J{2gphtYt{E;$+K^;9ac;Yo4f`N!$-A$f;`fcdnHVll|f*vNZTKe zUOFYQfjrv4E2p)`R>pxQD?3@7{yV71@-4D?;?G-10V`X;O-b*YY|a|U&f09QPfESD zByL>sy~N_@HPf>kxeG*xy^#H0$N5blI(>w96T&cMO7%L3Qcsok_rA$m*;k`Z%=H57 z@Zy_X4ioxl$%-e7lK~bE2YXi3g5@CII!2!kb-|v0OWw%*^U8=-iw&`0^0cqtfvAIr zufqx$mLM|GQ_r+g$P%5yx{;nMPdw&%q2{K!a8N6OE;X_vi*k@exV-Y_!l~S@oxRQR zhP+m_>v3=}ogOmIsuU+`k?|0fx+=(4oAKGo<{GG^B^ng9Hqd3pF|eYqZ)HRVtaQad znP07>Vye~{2X&}BlPkm*r1668hDKH9B#X ztInU0V)Ri^-RXrFU)e1eT7W^|gD4zhp(PM~_CFA9m!#7Sey$l>83k*L)@1<)o_J7o z(7LuEzw6$a71#fxWZm$#AOdum|FwJiBFIW5*RsrX>o zs!t9S;y+v&rL3ZHtRN#7sxJ4g@+rrF%=^a?al0}V>UMJBZAVMi#K-zK^(&j{>|Z26 z19k2jZh%MWbyPs44Rn!>FU{LP)al0s)ENX>os!Q$ zGoPfV%3+{MHX7>0gHeMHDWxkrP+tr!NnEM{E?#toD)y7h_y&+Yl%70g6J#8QD)HO5 zzz6CQeRAOeNB9L)nJ2$qo94K(Tc+ctI%)|~P$kyf{dnQb$}#C;^nN^DxefB7fB4aU z;?)(6edwfm=on&xtIdJzL7)6}qxZ_8Sd^tDiMURTj?;TU#iUc&Pp)rU*_N8TvU6Ce z>6d6JL_BUccJhfrqz5XitJdNT^X0+&f!nCEI;%aTqKoxz?D@A3`|0p73 z-^%J(9pt}&3bBw!>8&7J#@kM&+}yI<=D>=X;q0nU7FCOlLLzjoM-r9Wdz;58R^O6&z1FPo#8t}p9caDC%JB$_e z!3Q7y!K&QbFWLLg|DF5ZHwWH(`(D_AwRby`NL8|T-ud&FyT#As@)8SqI;nXg6~<0~ zKV2>+b5i~8&{L&j;;RIH*1t2JN5&7=EBQnZ!JXqH(iJGP8)e1@tsgcrfWmGsH^O&A zUDeiuAB^(*va0BfmE$Wih(?lrOI%68tg5ZtXxIf=e5V&j+IUX}_W>Ebm9xNO_SMA5 zD+YH_XaU*#jfq#H&woAGl>cFJ+e%ce-oLhH9BuBUYs5PdvSPIIKSH)mLex#=5=@tj z&G4ZdSz!f+2tVRO_DKH}0n7N=S)cm-KUvbh(b*&BoiG|MbTG*rY!2I`^&Z2xD#Tgj zH^=N`iC#vlQ~yThKmGU>IxZ|Sg@PA&nS&@w9QEcY-r2or!&*7s%Xr6ByMXl1E7CFa zF7*ZQrCvmjxoG`#<@e@f1TUjL6q0n{54}Qu#k7zizYth>2<(4H7yrYe7<-ix)f^t? z-YiV2x2yW$GO*kW5bt7Gb-~zpCH>9fJsAVUViz4oc>2gUs?bp>MK`8;2Qb_5KR}(! z67z%6SJ&@F6M|QEi$wyxoc^#T(`MGMqicS5>v=IS7ctbitTMQrjGIU)y=TE-oTX)5 zp@sZ|rhfOo2JeyjOZguuUt#n#96BJ?+pdDv7CcNaH-|c_SBR*@$VrvQDE-IEahtN0 z;AEE2NGP+pJ^HHDW0a8|Rz6-Y>=%fN47wJmUv|N`e`|T$wnd4m>|T8S3cg=vB;m*F zFClgmsiqs_h51CDWxqYM+-PqH^{ZZdGK6lC`FzpQkyRd-wW1b>2+g!=j6QU5t@loe z>|v<0d#kWt8uW|1h6F(U#z)M`(c+KL&(SpT2gy23(@N7Lj2nD?r4iK7>nwX5!E~*YO5L_pi3Baat zLQnd3^fju6YUp1m##54dTB9@Q=YxksZ?Vnuk|_lW`^ebS*p9(uxUOG3mK`&;CuY3H zDpGiQFbQKJ^l>R}CR2q)C`zRChXCwp*266^lxc;xqneFxr^~(%cIbwS%?DmB)#wH* zYxJi(htDPHA75i2>Y~_|{;9$~v3!Py$%-C%#m_Lr6AwvK56%dL)_FQ~b_@+IIyzoG zj*hK&+r}=rDPL4JTH|5iNK0-La)2fsmGa!Qy_GdETODzt@9mOda)ltDAp1Q1Lvg|4$EO_`%fr40o zLJ)yZ^R`=lRpV2-smv~vpsUkan!r=H*1-0Va_ov_r4i0cXH|j`8yEhq*V#N&I4AyN ze~N_SLV@d8S9=-5rH$SqEbml}ri()O9`qZChiw(^}S& zc%jw;8=mgP*)WD4%fB_sUxSfdRMep&b1Fpa7l)oEVgG`!@HAKc)S>lKzo?Pa2B%Ke z^_92q7mEySHBg=7g7Mx~P8>ZsV4a!$+~jtTVFUHwdWcr|?C;#8%dcZl>EHo7Iz`~7 zjgV3ny8yrGR+@Q?UaMo;aGRKYP7m~Ph#76Ml|DV#nPG?y9yT~~M?&s2`-=_cX?vOx zHva28xz&y-ew@%kRQ*ng3KgIuyJI{=$JN8Zgd%EiTOV1qP($Cz4!zx+A1S@BX}60x z7~)v>3DUuun`Azt@Q4uD0e;we)!__jgoTQSbK`@DTQvMg=`P*1zBhaojn|IZqYhHc zb86`4d|40gGfVsRXFL%Tp{ZZinNP3%#W^&Cs!T-h8#A(ZG~r>XJSS#M(K#wmFQj4p zQsXCZ@iJyH>bUNXPnw$IIb#Si`i$Z+!2*0ZR^XZ@D#@s*&*^>6ZE;x&oKTF`=%e8z zdTBji{^Uk^fW8W5D`B|T%`$E|Wo%aqH#9COZ_0YYbJG{bBy-@xnllJ>Vh|G?52~t$0uSq zo`s)UXd`~HV~X@KH))%weVd4vBj)9xfB?=p`utZME5r^7d{13eP9LKyLfW9oh~*{O z4A2tvwDDV**xomt^W@zzE?29^mByVWhamn-he$O!#KO}5IYgwOwPb2@fG!tsgwjUY zOTd#yTGI{tHSYF8`9+P>n^PRCLWOl94t^a5zm5|kb}$EZEiM^Ij`eT8pixijnA^A= zn28QWn-$TxcEj!FOEYWbEYjmPvMi)@VZu3}jb4sJ)yexAa$t67_83LH7Tsqtaa%u8 zWSlRb3?i%So}QA~1*k;{Ub^y;o|c+)+<`cWHL|Ptlu#z zjX4-m?=Pm0$Bg-B?~rKxBc--Fuhg@wb7xtvu1MD*bDi4TD@CEiWCzPUH8aVV8HIGL zBN7rg*?nXC6ElI2`G*7itllc@WMvKZape++ZOo}sr2(TAU#q5F^dt}tuwIoUT%?Mp z*^@jEn0g+FicfOjwsM@PwF=i+2?XxnFfFV{Y}T6flElLblxi`Ih7Dxtnlg+P*Q&=V zuJ-E8Hn^NM>(`OooIxrPjy%DY7hKs(k$ID_>&& zGGTn~vwm2S1H@Met{sNuPbp@?Q@l23$2)@31%v0cqJnsv!RnTLC--*5DBK-|dtRBC zZuoKL)SG7|lN%#o>g$&a3sJJuFQ(M{xXPz1?*$fgqn&e|Zp|zPyZW%r$BXUK zFV+byW2NGDx=)(1*Joz&=PQZnt@lbksVk@yt2T@l2YfpLL^Es))k~Kkb+?5Ku|rH3 zwkAvUTO}#H-jQO935ttzJDM5L?#;rI!XBQJhwZ}3mx$_5DDe{dR*42%SLx7>LgOe{ zbk#Ua&g`m8Joj3l2om7JME3++BO_Cb_X3Fnz3OROZDq?u+>G{z5dXTibKH{2y3*ck zLCAt$?&vx^k;WfWv8t=6ZqmY&@vYO>Y;y|f;Y{kwclDy5+-?q;P6d&HS zF}#+l>uBS4-nK3+ln`nQJ&u)L?6bZrG+i<%Om>VO)y7c*oAG_MDy!gjh4sZXDWMHf zuW#F43vG{>AY}R6?4r1^v0YN~X;ozDZLc`$Y~@T>NW#q-Pa@$a9;3;oq)ue}TQy4$ zR$tR{vQMG|+b1gp0NQ-q9ILo3$(eIG!tJ9aRC9NRq$-QZ_1G7p`;rZqh-vMmd(oa;A%LaAnZgl6iYJpdSl{J0C8-BCX9wVKkLqdkeQIVm z`GH>38oiem>RcC9-%s|xovb7KcnpW+z8=wk!6il7b7c$e1RzkljdJLt9U;q?w*doh zO91vzojDLRWG!g!HOfBHDbEFWnm2k!i=A9ZF)npc;~DYflmc8=L`FPT5If;otgB5+ ziQ$Cq-GO>OB=iyC8%u4_^=+SW*Cb{tvLespIfhC^{F~CN#$CEH)oM4bp|#x2K}XC- zG4t8u)ijJkhQSyGK

K+KXR~lJuTlx`B*KXpsgzjhSQhIEkm-a5&#iMhktC+o-Az z(2Zumv7Sf`il?r@j}r5ek-)}U z6-Kkf<5x_g$g0JEy%#7YvXP~erXXoy{?{Oh`fHG6nJ-k9`B)gA0OQ{+hV5qKGkiJ3 zZ%&qGZaX7*4Ip*+RFSH69vjTDDrR@x_EHblSmry$_FC+T$OvaWT;2*YEgf6%zN$MG zd9Vu42-4P=;*uX(BfUXPkM<=ZUFv49^)R}oH*}p5jQO~Bc{Zvx zyLMdij1UY0-%^`s3+x209g;?Q3?`EsIS|w0 zqAqGzj{<+%ue3MakQboqt$}@k+MBc}-xrU5bERZ*#=}{?P=n=Fwj1x4Y6q5%XuWhd zN+zEMxY);-^;o>yV-b2w!KkdEnP2OrnP2Ti>z9mY0Um_)s=ZxDO;cmYvKLEfR(v1@ zx(Yz(Hp07X`#CBKB_%hi0@CD8U4BxhgEMPaD~l(;)IWZ8_a2duZs;e59!>S`OC#Nu z8%Gj`r6(Fy+tZBSO}!)iF3ote%gc;(O99`GIv>V*_2lxb>`&|c)c@OVd_DCJCWlSy z$}WyK`}`g0TWQ8{Pp_kFMC83s;N?JcuE&@@p5pc#mW8$7TAJsKK4*jTZUOwBf1CsA z{F}-W<>}JiS3iH2x0yA>}hCDh#_CP;Vdd}29+YEH_|Bub!Yr0Y( zqd$mOw;QbicsCcqn4^Mik@jP8i}LAX8=Tt4Lq~rWob(NnwoJww~K@25s32D|I6L++&vQ9=nOe zr-NCPejU1ju87Z7Ph^b1EhEu;qLm|gE+KoGXUh9>+J4^Jm*ZyK%2RiayU9DNuZ^XY z`g!6c2diD|CeDq>V#Y162DYU2DLs|N_aza6*3p>Tb#3VbJTB~Bfy4WrKBu!?|ou$ z<}%9=6$t6eoB%bj2^g>IgaBFDcF)>ShomqE#KJNpO2(xcs?O4oB5hxP$Q_Bp5SzTG z*?)#=OP2LE#uIL{{pVj+@N^6PA-7YJx2Z=kOuMO4`$OhA)ni$`w4ixz$J30^uN4n0 z{hQ-NnQ7u(BypoKR#h~h=gcRcR`G98?KDd_`}KRdKIYRkVw6BrsPlmvcj-@g3fUNO zgiw)aq}RB)NLT~vH}P=7HXW|5W(+Mh1O=!EYIKn@W-guGQ-tYFf1}JW1d13%)OwL& zelgD{O;uB)OXT2OvH0!b$bMWj^E%bZNv+`%;A~>9jvZm2Od8Z8x6i{Lk@!;Y$1 zeva0cp?gs1<{VyeMmBvFm*`96l^p9;yKN^Sa^(qO6KP?prSmMpSE6fd{_8^Bw?sF4 z6wF?|jLnUo;J;uh%QGell$K7+jcQ3>`JEoJQ(F8!d5k0XgMo)ET%K+R=3>9shsq^d z4KiPRt%pC>Uu#z_+pMSQ&Z+e0MAuk+p8)lbe8T5!+%Ak~q^!F}{}-0lP@>ekPU#j{ zn;XSg&zY<89{yyScv5r6tS>-#M{bby(`_*^z<%wf;JZ1hRsD3BjuE#cU+>#NiImwD zVle@1Iu+TB?!}<{@#t>8WusW42x8dl74SqhXY%7^O0*YQ)Gws=umO_H15r zr9}B5Pd!rz0P4JM2tUjyL$aBBF%B%^r8Dy7(0W0DETEb%uEscYt8sW$MyLuK4}9>F zi)1mzQC+fhl5Jl;j*2h@@Z^P>5aJ+OWH#s0adDx|on9a#p7h0d_qmdas1=Jw{XX_lUI0hyG<7x3VN6(bvcB z=(Nu3*05rx^#L{U9?@az9PggXuo8Q*jzm@-oYC8Q<_%}rCU$-}Gji_f0T@MGImp~U z^h>Dxah_U8@MWbl6C`6gQ+Xoqfs0c+8XbLJh$o*Xm8juQGk?IEU*sidYGuw1zs1S_ z1da3!YrDMq`(=9)`sd@Zep#tOK5_K%Uges$IuoyuW(@Jnvuta-bz;1@BuefMgt>~U zXxTArd7h;`b*eW!QvMumPutI=o(pzuZaw1K9FgBs_``V4Sle7Y#l4pQd?ICLOx2&5qX{9^;#XRef*qYKr-pGh-x7n^2wCa*X< z5SBdSm2V1-7pCf3dlsfx%tI>rYZa^QbyaDL;OwZ6kTy8ymF2Rb*~DI#-i`~U85#$I z>X@+uwAFjVru8XKEtvkl-4vr=e>w5Kmys2eGO!hw$JK{|S+@{SP zlOG`)nqP**9{y8mXbJW2LD5x`Q5V9Y6ry#>ZvA4ayHhch$43wFf1onYEipY}$4F;n zT^2UUU$V~OMJzTy$5Gu;vauQQwUEtE<0;zt1P8S^Qb45|xtdCJKV;G)2ktTeg2{|@EJK>m`$VyM_L=>X-N^G0 z^<>#p#4r%NcC2?y%@DzFQ>Plm2~G~b;?$@7mBnM z<3+5+AZHl8{7*3ei+@Z4@8T0g>gr?nXa-ug>a5sP7jThu&1eX;J7io2F$f1R^yD9q@s< zU{xg1Y@x0FRsCU zAZgQ~ggWsZb#cvjgzbsn@U2||e|kza0k9tGq8hQ>wTi+RhhZneeO*7E_$BGTRw#(8 zkCq5d)*+ueV<(b*S?2*{Qn7BvjJwrWiDXTQ9a~a^)B5MFbOS%6eqLcp?px*^UCHNk z^lDkk-;(-?)Z)wG4s@ld{1E#4bUtD6XFIakZfg!2P1MITOpsMeq%D-2$k-%J0ub*A zgYi>%%x`Ht5V*ba0phlK;_bMJL_MCIMDGtl|BKxH&h6WE$zDb@4Ql!IH`dn>r>s)F z7a?GjR*pGold;MFfKFQTnkZwgq>^c#tfmAT1ARM~B5i)Z-j(Y^I85LV+BC}4g}M+E zRE;kJ{Ovw{v?sHlR!x?f2jq{DDykC+em>31nL57r`G>O=Kp{h9&*n!IwlyKm90uOB zBpvFs?@|6dpX?KmnpGIoFaF^R5>*r2*56D;lf}ER0}(sdp)i6X>Kdoi)1sDX!!`>` z*^z9)l>wKaraevgIj46mtJ!56uw9)|-(2WR))5;&2% zz84@?fpG23a;<@C?Y0ABBjpOp@y`5PPO<&`%&YY_ekY|ziN)4=)UBvmGWw1wNW1Et z-8+_C=(bht3gA&+x$bM#(yPjTt2?lqUcL|2_>Bl&AU8bD$k~ixm`zjFIlUTTKG`~# zYKqXv2G~^5uNgVyBwhPF@KN*ot@zcMtJTE-)AB%Fo0Kq9E|q>|3stwJD#Ek-~ny|KP$kI zo>z;A*U4}Oq5zvAvd&Lk4$Go})O9OLz(jm3BjId-r(Xa%6O+@+$SJ8D^IRe@2-cp?jk!Gcl>O9EQ; z6Mm^}GouuSK41BwU+BW>q-rwK!%y<^)GOW zT0SXH)W>o3Te&{WW6ZHZ2eNDqF9^Mp%xSQI9xL>_To(k0jDtfu@2)f)CT$UZnVnQ;`BsPOBRTE*59<(Dva`dy6HUU?AoB zsXwlVMgjt{9I~mn`gzf$DX&!Nms876afIMuVt%r)Pp<|#dE)DY1NaB}V=Rp{n7NEq z$--{jC&UvR0T*Lz25J?f!R(1af5+65%xHEm%|Yyj+pfcJpF$o*b`w#eohXq(QRuc= zN6bwo^-Yill;lD;7=~n_Po@0=Rlk%=jUzQ~TO`MNeuFBq*aTG~cy5FDw2oMy%K_@4 zeO0iHm@d@PGV#}MRjdo|i+(eR7sT~4iWkztMSG@eEEAb<_Q09=ee7dE{Di7>@)z@V z$TuujPXZBCK$|_LYuepJ@iorEZ%}ild1fft%d1v*a5=sDOOEQtOZGPXx@-M#n{NK< zg?SyuppIZ~{w1+=QW59X#EhH{MUb_L&I9TusiKKI`u{=KG%xkmz$VguwC?y&f5zj@mgzlcGXERKnzO>NsdQoUGm3xNmZ$7Y8y)a+B!0?Zh zJ9e_4Q!nEhomq8d2zT^qBdy4$dqf5<*ot zQ+_FWmnFxYW6lz9$_?}Ph}aYnF^^7kt&1iNqBB4OO za({>bYBUUQ`|?ZPK%5q)!u@<;6t)rj8-n>CrL6woqUyrD=K1C5HwAbnCP5`Y=-{*h znRT9U#)qfQUGJy+YiKXSjIFaid5nz!dX;R7a5j#+ho#(VfOKP+SsT%7kGr0=xuu4k z3yYk5i~gAZcB7o(ItBM|Zu2(rZ9bIhgUWZLp=rW11i?8+ChvgC?sw32FoK;2K$nNB zbMS5-wv=bNb0?dKT`aD}o)PNuho$=7U#ctt=*aUzQ{S3CQU(N8lfML3-%^yJrnWJx zq)mW5A&~JD)~-f@rzu@*@ozp)$&;9;givKag^=YTeGg>zr6%TVIkrLt{t{Suq<-#V zCp&)Z*P;p5Um(3*kry*QF2YeP@=^uqrXtB$>bch}>{|QhX-~_a21T-it`-H4Ph^nR zkzK~cda_H<-v}`vi?mc1E8^SdD&t%N=~1sUOH+{J6Q_70J1(*%vQO!hN;lD9|4<4s zg;UML$4Y;TlKzAL8rFT_*Vc$MLk`xD-|VH)oBpmyz9?MGSyV4l0OqT?@)F??!G2Db zGix@h28!wS3Z@tH5CBWP&mEjnlVyZlJh5&^m1;So)7cJrli4#CZ8c0iU}Hz}%d(tO zOb8T-->6ntND|}7@dY|+N%$U25Qe@hJZOPvQ?cesxf_B+xRq+$CBa{3kaP-1HNF2% zF9GOp5M%XiUQ}U0!#Zjmn^ss;%)%Dkv*1uqT3DWm-Jy^yu)-Z%_OS7vF?pAq zQj$8a^(y!4tMsQP)ZZ-{+Ko8ZuJ_Vpbj=bm={zg#TV9f!^}KDNzo=bthstn@5$3+8 zh_Q4SmQhqg}n#!XM#-tLupB$wi>x%?H!nYR{467=MxAEnyG3$2&QY?U3 zLJ24cX9^mhkL#UXuD?20xtvwcxn$i;6QA)JFi}bouFn3)MieHlUBQ^B93;q1$MdQMJcV8`_SzjtU|Y%S}wt_#IUZkZb*pKL3p) z`ajaVSS^Sg`8B};xFM_NnUntt5BvqOCQ$f4Jm|}DH*VF7+AZt%MUVeoh4fn!FP>k^ zOVpe4E8Y=q2wgNUvHn|a^bV(nsygOoXJUWkoB!kR1;CFt-gQ84X(&fvv9 zhn)Od7i5_+-uRL$6=L*3+9QKa1eVf!HpfGt->8R&GyBBCa)1c0Kt{~xQ$z3I{}C9n z($A;)taIoy{Vu2=)MAik$$COiop8T43Q5Xug|Oy!zyOhl2{gSKaSu56|p6J8%ny3%oZgb zmmzXB>z&lE8>79u19I5UyBcM!!8vx-lb#B?%M3oebI&vlE{SXexaz3HYx}r6!Y^L+ ziol1k5{`CqL~Ct&$TlqfAm9x@0$~O4BK>AXo!RsqIa??6-y?l5Eq;;xweV&`2Zq(r zC=*y<{X03p@AlJvi566Gnt_R;2FjWF0nG%um*yf`iv}Vxi2_SsW}lzO0AT@H=4XM8 zU?rSoy}oj<2%FXkl#!7|DC7;biC#G=SOX9)-=WP^1!!`*t}ttiA~Q6%m)wY3;vu?Cpj5aO~)--DxRC zAbv>NU4Zo^;?4k`JxGeSAqvH()5QC?M$+^P{EHaX=uDFCZe!^A7f5h^~r4}H|`C+>CwE)=NLPGy)t9aGD%-jnve3oJ^ZWZ0$%Ih=1HUjy@xv5>@_XLNZ zXI$Gd3(LpFVJ}9+v;;GSC8mvdnRRtRqAHw^a3`WF?wcx5e5m*O{(h4-NNWHpxut|i zI2&Mw^dFIS4gDeNm;PzJw=(CR%y3(R9A4&G>i{O>D50A#cT%KI^TfQ3XyxonR%dIfW>YPQ=(^7; z@3mQ;m-_KltHM;=r}7K}>lLFhDyJJGA8?Xy2j~S?@#z4y>?++h9Pl4EvdqfV8D28= zFPyELTyLYt=A6zkmA={^C; z4)ZS3HHn>K^I@rCp&#RvvK(=f;cnsVULCPeuD@C`bvJ`jA>QEK7eFd%sF>!xt(d=w zxwdVFw@n39Z5E5EU8wnlr*~nWvf(ZM>uiQ?)~X%msLO3m5}v0DV7@%)uO3Q`{bvmu z*EqU|$oRv#^h zZy<2ZOg@Kcval5G%U2IX4sc4QewuII!X-^Ba-8sAirDPuISZOgv#V!yg$M_(Jc9Jg z{uV$HvXnt-LR0KJfjJhij!Beo9aW=O@7!Sw0qx0-su_2kDfl&coPy}Iz+lg4dGpEX z9Q;xJ*BpGeyll9R6cZc;N z;*xitMFI{d;_X*3^2OBr8EU?&Sd+gf(U_y=uP@$foC_^5+Q|OkI;F^(1=33ed&MmI0a*QgPT`Anm9H2maCkRCWM>np9chW zbL$K?Dv9FAHFEFU4;va1SgxM2eP45u{FG;Re&6l7`C*T%cbxQV9B% zVtaHQ%r+V2Tbx3iAdM%CT}gjEa(Ugj_aXMp>Oxsr4^{Q#L3+|pPdaTjAlIgD@E_P| z?dD3+7p$3+y}axt^>LMSmtdZZWY%7Cu$p&|)y!mFDpy_aNtQmgbz#{wor#ou*T)IQ z{bhH1wDCbRf{z}Z$ej0=9+E(18;6e2;gOBX-BF~yZLjb*dKVg*n|{`b#c{ndEs;}+wIJp$(Y z#(q8GeFD=!R@Gl-er70nbK>Wm%ZShLq&FcyQ!Wdq$r^5ilfm!jeBg`&yp2HQvlI2h z!N$)tHe#nGW>(}<=`_9o?$&pjQW{NrI{nejps$l=d0Smr3ZF1IHLfS5nA{Sn$IF=c zty`P@x2Xlegl(^-PbdpZRW-t+By^dOi_te%MKOz%z5TE9YLLeaNBm%hFplxCY)wzx zp$talID3nlClER%vMP?Ndlrocv>EP_uJ>)cWSe=qNq6Hn9J8(P>j6R%N`6E}rxLtX zv8-S@OWBslal?o{^b;^PQz_fjEsGg39LkwY7jZI@C@#J+Uck?sFYaUTrZXSJHFBt= z?ZrPR3#S?v1N_&uxy-l?T9S=Ji^0XpbsGGlcA7J-0XoQ<3n!gjC8G~;ek^>1>jvf~ zVMS=@sG#o#HY#!W0q*~`bFV>7ooO7v+i6#4N07Ew(TdQ{s%*WGQU?JwqHC3QNv9^2qYxz)KOPKP*5OT>y?X2lq{D7Le^3*kU&HT7;-@oiGTqykRTzj!JXai zu3tJkvtK;_^C^=r%=3Gmm-i%j&)@PZb#?JwQlnkgrLNlzqAvutJ)(GyflLx;Gmi|% zY&Si>U{78yEBt1*Z9%BZEI)~zpRcUwQddSs()Rv>@2r%LA2 zz~Nj$n$$h*-T)`w`MG^2#X)E?h4kC!Q5<$y-L;Vph>EXy+V+y!8+egJg`M&e5w*8E ziaykO)zj!S<09)^(*#+vk(Jh_5e23VWg4|kPs177Hg$=IQsW%U_p!5>FU^VbilQY(I@{%cJG)m_5I(QDKjK-l zk9vW8r?$^5SX6Yq*w!!q;SKZIq@=3+zO6ZVVSag~o{m1@c^-B12i?kYDC^>)BTded zU)I{>c!Z}<#6#xTgImq@nN(r?e!2}exWm3S`pLvbZ(5~&b$F9}=EwC{4TXPVJ>^_= zqNT{qHl7HvEVtcO%BhBNUU{G8*4*KQ*l{v*EUcS!!ozX$ z?AY#Zo2Rj2me_k@^>FUQ)=6Rb9r3b(VN%RgvgKgZzZRT~6YQc(d!RkuVE&XHmsua_ z$tkjp5tDD8e)h7qsqRog+DH0^2=Bqpyb70&F26+5AzwPLZgS{vjE}muCN4LXHI6q) zUz{2-G0VgOa$O)ll)IoVeN@trR8oAGD%Ds%XG%tgUyY21H&6JKdF1aaY_uwrjyk5B z;tSX-hFf~pr>lGr088sSk&mDCNryiUraBPQPy?NUBl%B?m5O-Y&7&} zGTk*fqbvQ%oESPuruS7;ISZ^6u32n;gLMooU(7n9lJzLYrH3fd1Et>^uUzQUWUDUs z304m*jGJOhDPb=sEQj|yr7v>wM*G|9mYG8-v+weXbA~0!LKY>OK%T6P?zalJ>RClXlFrC)T5Oew>ZunvL(^XCX-N zoH^2On7sXCNPeejZ>s5vVZDfX*m5*o7^skT@#&RaB9EGbRlBMWc9c`9z1J@v%qzUS z*78(mYG1d+cBE^s@(P2$-EA4-B&KFLN!raw8j~F$;ro+m-n)DO{Jr{1d(8oc3%DM zn8s5|S+kF8{=cubL}pUt-2=<}Z-48!I^1Ik*{_Ty%vh@JoKw`_^4mEJwP~bdYg4b% z=B4H5DLK+|;acXd5Y`T7yG07BSofC_>mVEIn(E#gPqUotb2>LI?WmZ^Q@JJGPv>5r z=@b+*`tI!)rRJjcP(fFz#MVfT31MX{X$>-ZMGnbx89EoTC`?9<(+OLw%?ZCY#PQ7y zZ|YD$gd+CI^p_E`Ci zMT;$88tJa|XmV6@6=L^_A-}=;r#DRT?S78y#cR{2f(?nQU&(?Cr*;^Uz8r8YQ!4*4 z>Cqu<)A?%-K8Zi>zvIqlQ7Ki^gC1w}8e(Y2L-vw6`xGg>;{M^a=`2=sl%!7DTDL^t zT4Z>+uIns&p@jC=&tmrUMrIfIwvUf@IA@p#GHRX+JK9Ah`-QGc8GW(5D)uJ-DxQoI zR>rascF>M>EH?HhiAk}h#%MRrW_b!Vv~7yR3(Zj-rc=||B&Yu2wAl~P$)ch`%A(Q>AKKb~k5WW6jR&-^Jez5QqN3C%t51r;mQk$GqP8C%;xjJGy zYhWbEzp1jS(XQ&b^_Hr8cCNB(%Pn_S|J+d29=o~csZA-RoK;lAjF2;WW=VcA?T*AT z=a-we<~32}2eL+b8-JmNmXiVKwEOr*6@d% zuY1Yv=KtDGLRrBs{P^cRD#Pwfxw>V}K25S9|@Zs#OdokUqaqv z`PBEK@sxh%O0I(WsN5ycVVf1NGl{M{XS$xFDIZF2sxf?itj3@*ujMoKWgFh(^z+VHx_ds7u3?(oM20SG_< z0uX=z1Rwwb2tWV=5P-njF7WWkd#@kN_;jjc%eJ?@Skwmr2tWV=5P$##AOHafKmY;| zfPnw^HnZQKJ{a)l3F^X)@PGgWAOHafKmY;|fB*y_009U<;H?)pN_P9;^+N(LlOo=K z>#Ie95P$##AOHafKmY;|fB*y_009WhMPLAi00Izz00bZa0SG_<0uX=z1Ry{x0R#pR ztKX;=0uX=z1Rwwb2tWV=5P$##Ab`LC%m4%+009U<00Izz00bZa0SG{VSON$PAXdLo zEd(F{0SG_<0uX=z1Rwwb2tWXV0hj>@KmY;|fB*y_009U<00Izz0Ko(h7(lRwqg)6; z00Izz00bZa0SG_<0uUg)00ILDKLP-N00bZa0SG_<0uX=z1Rwwbf(alnfM5+rxe$N= z1Rwwb2tWV=5P$##AV7El1O^a(1ONa52tWV=5P$##AOHafKmYE9{0MRD^^+Nyx5P$##AOHafKmY;|fB@kH5EwwXmZM+@KmY;| zfB*y_009U<00IyoxBvnJ2tEKP9|91700bZa0SG_<0uX=z1PCX9zyQLv90fxF0uX=z z1Rwwb2tWV=5P$%|1rQiO@Bu*i5P$##AOHafKmY;|fB*y_Ks*5i1`x03s2BnefB*y_ z009U<00Izz00f9FfWQD^_aD_m00Izz00bZa0SG_<0uX=z@dPfD-4?%Nxjq<~s&KWM oeuWDH5P$##An^YbAibM-bEn;__da_Dd;kCd literal 1421983 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%0AKk%?jAFt4v@=yf5`ZlfFWbq8WbH6ArnWTDP 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 U8M>S$@#=?>Gv$N=tu}X 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

nyGbN_!V$2qT;Enoyv)5 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 AivR!s 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_small.png b/jarvis_small.png index b2610b3a90b71a2fffde7ca703ca40c043ccce86..26d4bd99505ca3dd3f0c42c3a7aa57b0555dee6b 100644 GIT binary patch literal 38029 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 F{|9w?v|RuI literal 36281 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 From 2ffc0ec7326bb48e4795fd51402fb4678f2c7635 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 14:49:17 -0700 Subject: [PATCH 036/365] Move avatar_url -> display_avatar --- jarvis/cogs/admin/kick.py | 6 +++--- jarvis/cogs/admin/mute.py | 12 ++++++------ jarvis/cogs/admin/roleping.py | 2 +- jarvis/cogs/admin/warning.py | 8 ++++---- jarvis/cogs/gitlab.py | 4 ++-- jarvis/cogs/modlog/command.py | 2 +- jarvis/cogs/modlog/member.py | 2 +- jarvis/cogs/modlog/message.py | 4 ++-- jarvis/cogs/modlog/utils.py | 2 +- jarvis/cogs/remindme.py | 16 ++++++++-------- jarvis/cogs/starboard.py | 2 +- jarvis/cogs/util.py | 6 +++--- jarvis/events/message.py | 6 +++--- 13 files changed, 36 insertions(+), 36 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index b551225..b7b0a6d 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -43,7 +43,7 @@ class KickCog(Scale): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.display_avatar, ) embed.set_thumbnail(url=ctx.guild.icon_url) @@ -63,9 +63,9 @@ class KickCog(Scale): embed.set_author( name=user.nick if user.nick else user.name, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) - embed.set_thumbnail(url=user.avatar_url) + embed.set_thumbnail(url=user.display_avatar) embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 7f5adf6..b406fd4 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -89,9 +89,9 @@ class MuteCog(Scale): ) embed.set_author( name=user.display_name, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) - embed.set_thumbnail(url=user.avatar_url) + embed.set_thumbnail(url=user.display_avatar) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) @@ -117,15 +117,15 @@ class MuteCog(Scale): ) embed.set_author( name=user.display_name, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) - embed.set_thumbnail(url=user.avatar_url) + embed.set_thumbnail(url=user.display_avatar) 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.avatar_url, + icon_url=user.display_avatar, ) - embed.set_thumbnail(url=user.avatar_url) + embed.set_thumbnail(url=user.display_avatar) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 2095318..982251e 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -112,7 +112,7 @@ class RolepingCog(CacheCog): if not admin: admin = self.bot.user - embed.set_author(name=admin.display_name, icon_url=admin.avatar_url) + embed.set_author(name=admin.display_name, icon_url=admin.display_avatar) embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}") embeds.append(embed) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index c807efd..9196011 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -70,7 +70,7 @@ class WarningCog(CacheCog): ) embed.set_author( name=user.display_name, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") @@ -117,7 +117,7 @@ class WarningCog(CacheCog): description=f"{warnings.count()} total | 0 currently active", fields=[], ) - embed.set_author(name=user.username, icon_url=user.avatar_url) + embed.set_author(name=user.username, icon_url=user.display_avatar) embed.set_thumbnail(url=ctx.guild.icon_url) pages.append(embed) else: @@ -144,7 +144,7 @@ class WarningCog(CacheCog): ) embed.set_author( name=user.username + "#" + user.discriminator, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) embed.set_thumbnail(url=ctx.guild.icon_url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") @@ -171,7 +171,7 @@ class WarningCog(CacheCog): ) embed.set_author( name=user.username + "#" + user.discriminator, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) embed.set_thumbnail(url=ctx.guild.icon_url) pages.append(embed) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 28f8a04..33b278c 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -86,7 +86,7 @@ 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( @@ -220,7 +220,7 @@ 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( diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index b03666d..084e773 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -43,7 +43,7 @@ class ModlogCommandCog(commands.Cog): ) embed.set_author( name=ctx.author.name, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.display_avatar, ) embed.set_footer( text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}" diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py index d6423cc..9453e19 100644 --- a/jarvis/cogs/modlog/member.py +++ b/jarvis/cogs/modlog/member.py @@ -324,7 +324,7 @@ class ModlogMemberCog(commands.Cog): ) embed.set_author( name=f"{after.name}", - icon_url=after.avatar_url, + icon_url=after.display_avatar, ) embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}") elif len(before.roles) != len(after.roles): diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py index 1834877..853680b 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -44,7 +44,7 @@ class ModlogMessageCog(commands.Cog): ) embed.set_author( name=before.author.name, - icon_url=before.author.avatar_url, + icon_url=before.author.display_avatar, url=after.jump_url, ) embed.set_footer( @@ -99,7 +99,7 @@ class ModlogMessageCog(commands.Cog): embed.set_author( name=message.author.name, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar, url=message.jump_url, ) embed.set_footer( diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py index 8831016..4344ff8 100644 --- a/jarvis/cogs/modlog/utils.py +++ b/jarvis/cogs/modlog/utils.py @@ -35,7 +35,7 @@ def modlog_embed( ) embed.set_author( name=f"{member.name}", - icon_url=member.avatar_url, + icon_url=member.display_avatar, ) embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}") return embed diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 3d75bdc..7f7c5da 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -149,9 +149,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.display_avatar, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar) await ctx.send(embed=embed) @@ -177,9 +177,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.display_avatar, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar) return embed @@ -265,9 +265,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.name + "#" + ctx.author.discriminator, - icon_url=ctx.author.avatar_url, + icon_url=ctx.author.display_avatar, ) - embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_thumbnail(url=ctx.author.display_avatar) await used_component.context.edit_origin( content=f"Deleted {len(used_component.context.values)} reminder(s)", @@ -296,9 +296,9 @@ class RemindmeCog(CacheCog): ) embed.set_author( name=user.name + "#" + user.discriminator, - icon_url=user.avatar_url, + icon_url=user.display_avatar, ) - embed.set_thumbnail(url=user.avatar_url) + embed.set_thumbnail(url=user.display_avatar) try: await user.send(embed=embed) except Exception: diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index d2eb830..4807be7 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -221,7 +221,7 @@ class StarboardCog(commands.Cog): embed.set_author( name=message.author.name, url=message.jump_url, - icon_url=message.author.avatar_url, + icon_url=message.author.display_avatar, ) embed.set_footer(text=message.guild.name + " | " + message.channel.name) if image_url: diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index dfd6cdc..1696db7 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -126,7 +126,7 @@ class UtilCog(commands.Cog): if not user: user = ctx.author - avatar = user.avatar_url + avatar = user.display_avatar 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) @@ -223,8 +223,8 @@ class UtilCog(commands.Cog): color=str(user_roles[0].color) if user_roles else "#FF0000", ) - 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.name}#{user.discriminator}", icon_url=user.display_avatar) + embed.set_thumbnail(url=user.display_avatar) embed.set_footer(text=f"ID: {user.id}") await ctx.send(embed=embed) diff --git a/jarvis/events/message.py b/jarvis/events/message.py index a449019..74b68e6 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -85,7 +85,7 @@ 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, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 @@ -121,7 +121,7 @@ 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, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" @@ -191,7 +191,7 @@ 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, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" From 6f8f93e9bdbaa3e9d77cb6578293e764ab391a37 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 15:01:18 -0700 Subject: [PATCH 037/365] Move user.name -> user.username --- jarvis/cogs/admin/ban.py | 2 +- jarvis/cogs/modlog/command.py | 4 ++-- jarvis/cogs/remindme.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 1de0b3d..2859530 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -155,7 +155,7 @@ class BanCog(CacheCog): ) user_embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, + name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.avatar, ) user_embed.set_thumbnail(url=ctx.guild.icon_url) diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index 084e773..884fac5 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -42,10 +42,10 @@ class ModlogCommandCog(commands.Cog): color="#fc9e3f", ) embed.set_author( - name=ctx.author.name, + name=ctx.author.username, icon_url=ctx.author.display_avatar, ) embed.set_footer( - text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}" + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" ) await channel.send(embed=embed) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 7f7c5da..a25c269 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -264,7 +264,7 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.name + "#" + ctx.author.discriminator, + name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.display_avatar, ) embed.set_thumbnail(url=ctx.author.display_avatar) From 9c9bd36816b2ad9f1228e03b98e4afd86ee3c4e0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 15:09:02 -0700 Subject: [PATCH 038/365] Await async get_(member|role) events --- jarvis/cogs/admin/roleping.py | 6 +++--- jarvis/cogs/admin/warning.py | 2 +- jarvis/cogs/ctc2.py | 2 +- jarvis/cogs/verify.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 982251e..14d2bbd 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -77,11 +77,11 @@ class RolepingCog(CacheCog): 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]`||" + await ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"] ] bypass_roles = bypass_roles or ["None"] @@ -108,7 +108,7 @@ class RolepingCog(CacheCog): ], ) - admin = ctx.guild.get_member(roleping.admin) + admin = await ctx.guild.get_member(roleping.admin) if not admin: admin = self.bot.user diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 9196011..f81443f 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -123,7 +123,7 @@ class WarningCog(CacheCog): 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.username}#{admin.discriminator}" diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index b419bcb..72687eb 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -107,7 +107,7 @@ class CTCCog(CacheCog): 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: diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 540e65c..ada36fa 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -45,7 +45,7 @@ class VerifyCog(commands.Cog): 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() @@ -70,11 +70,11 @@ class VerifyCog(commands.Cog): 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) + 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 = ctx.guild.get_role(setting.value) + role = await 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.", From 68d29d9dd83c3b2c130e6db45cc5765b902083f1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 15:36:09 -0700 Subject: [PATCH 039/365] used_component -> context --- jarvis/cogs/remindme.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index a25c269..a5c7ccf 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -235,13 +235,12 @@ class RemindmeCog(CacheCog): ) try: - used_component = await self.bot.wait_for_component( + context = await self.bot.wait_for_component( check=lambda x: ctx.author.id == x.author_id, messages=message, - components=components, timeout=60 * 5, ) - for to_delete in used_component.context.values: + for to_delete in context.context.values: _ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete() for row in components: @@ -249,7 +248,7 @@ class RemindmeCog(CacheCog): component["disabled"] = True fields = [] - for reminder in filter(lambda x: str(x.id) in used_component.context.values, reminders): + for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): fields.append( EmbedField( name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), @@ -269,8 +268,8 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.display_avatar) - await used_component.context.edit_origin( - content=f"Deleted {len(used_component.context.values)} reminder(s)", + await context.context.edit_origin( + content=f"Deleted {len(context.context.values)} reminder(s)", components=components, embed=embed, ) From 0c382986cfae955d8930876a34529e363a33d77f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 15:46:17 -0700 Subject: [PATCH 040/365] Migrate rolegiver, use components for add/remove roles, closes #101 --- jarvis/cogs/rolegiver.py | 340 +++++++++++++++++++++------------------ 1 file changed, 187 insertions(+), 153 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 4047ac0..fbeaa4a 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,44 +1,38 @@ """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.client.utils import get +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.role import Role +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 Rolegiver from jarvis.utils import build_embed -from jarvis.utils.field import Field 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", optin_type=OptionTypes.ROLE, required=True + ) + @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) @@ -58,7 +52,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 +61,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( @@ -79,21 +73,19 @@ class RolegiverCog(commands.Cog): 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, ) - 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: + @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) @@ -101,28 +93,28 @@ class RolegiverCog(commands.Cog): 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( + context = await self.bot.wait_for_component( self.bot, check=lambda x: ctx.author.id == x.author.id, message=message, timeout=60 * 1, ) - for to_delete in context.selected_options: + for to_delete in context.context.values: setting.roles.remove(int(to_delete)) setting.save() for row in components: @@ -131,7 +123,7 @@ class RolegiverCog(commands.Cog): 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) @@ -141,8 +133,8 @@ class RolegiverCog(commands.Cog): value = "\n".join([r.mention for r in roles]) if roles else "None" fields = [ - Field(name="Removed Role", value=f"{role.mention}"), - Field(name="Remaining Role(s)", value=value), + EmbedField(name="Removed Role", value=f"{role.mention}"), + EmbedField(name="Remaining Role(s)", value=value), ] embed = build_embed( @@ -153,13 +145,15 @@ class RolegiverCog(commands.Cog): 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, ) - 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 context.edit_origin( + await context.context.edit_origin( content=f"Removed {len(context.selected_options)} role(s)", embed=embed, components=components, @@ -170,12 +164,8 @@ class RolegiverCog(commands.Cog): 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) @@ -183,7 +173,7 @@ class RolegiverCog(commands.Cog): 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) @@ -201,21 +191,17 @@ class RolegiverCog(commands.Cog): 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.nick if ctx.author.nick else ctx.author.username, + icon_url=ctx.author.display_avatar, ) - 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) @@ -223,117 +209,164 @@ class RolegiverCog(commands.Cog): 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)] - _ = await ctx.send(content="\u200b", components=components) + message = await ctx.send(content="\u200b", components=components) - await ctx.author.add_roles(role, reason="Rolegiver") - - 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="Added Role", value=f"{role.mention}"), - Field(name="Prior Role(s)", value=value), - ] - - embed = build_embed( - title="User Given Role", - description=f"{role.mention} given to {ctx.author.mention}", - 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_footer(text=f"{ctx.author.name}#{ctx.author.discriminator} | {ctx.author.id}") - - await ctx.send(embed=embed) - - @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, + try: + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.author_id, + messages=message, + timeout=60 * 5, ) - ], - ) - @commands.cooldown(1, 10, commands.BucketType.user) - async def _role_forfeit(self, ctx: SlashContext, role: Role) -> None: + + added_roles = [] + for role in context.context.values: + role = await ctx.guild.get_role(int(role)) + added_roles.append(role) + await ctx.author.add_role(role, reason="Rolegiver") + + roles = ctx.author.roles + if roles: + roles.sort(key=lambda x: -x.position) + _ = roles.pop(-1) + + 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 = [ + 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}", + fields=fields, + ) + + embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_author( + name=ctx.author.display_name, + icon_url=ctx.author.display_avatar, + ) + + 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, content="\u200b", components=components) + except asyncio.TimeoutError: + for row in components: + for component in row["components"]: + component["disabled"] = True + await message.edit(components=components) + + @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) 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", hidden=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.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"{role.mention} taken 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, + ) + + 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: + @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] + 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) @@ -342,6 +375,7 @@ 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)) + bot.add_cog(RolegiverCog(bot)) From 086af84cdc532b741abc573cc13630230a7d8ab9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 17:01:41 -0700 Subject: [PATCH 041/365] Slight branding change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f33d87..2f0b019 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

J.A.R.V.I.S -# Just AnotheR Very Intelligent System (J.A.R.V.I.S.) +# Just Another Rather Very Intelligent System
From e1917e870e111ebbec06635885d4b80f55e8e7e9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 19:48:56 -0700 Subject: [PATCH 042/365] Migrate starboard, closes #103 --- jarvis/cogs/starboard.py | 193 +++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 111 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 4807be7..81747bb 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,16 +1,15 @@ """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.client.utils import find +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.discord.message import Message +from dis_snek.models.snek.application_commands import ( + CommandTypes, + OptionTypes, + context_menu, + slash_command, + slash_option, ) from jarvis.db.models import Star, Starboard @@ -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") + @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,29 +43,25 @@ 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @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, ) 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", hidden=True) return exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first() @@ -90,21 +81,17 @@ 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", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @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() @@ -112,37 +99,26 @@ class StarboardCog(commands.Cog): else: await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True) - @cog_ext.cog_context_menu(name="Star Message", target=ContextMenuType.MESSAGE) - async def _star_message(self, ctx: MenuContext) -> None: + @context_menu(name="Star Message", target=CommandTypes.MESSAGE) + async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add.invoke(ctx, ctx.target_message) - @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", option_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", + option_type=OptionTypes.CHANNEL, + required=False, + ) + @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 @@ -152,26 +128,35 @@ class StarboardCog(commands.Cog): 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", hidden=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) + 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, @@ -179,11 +164,6 @@ class StarboardCog(commands.Cog): 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) - exists = Star.objects( message=message.id, channel=message.channel.id, @@ -219,7 +199,7 @@ 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.display_avatar, ) @@ -247,34 +227,25 @@ class StarboardCog(commands.Cog): 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="message", description="Star to delete", option_type=OptionTypes.INTEGER, required=True ) - @admin_or_permissions(manage_guild=True) + @slash_option( + name="starboard", + description="Starboard to delete star from", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @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", hidden=True) return exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first() if not exists: @@ -304,7 +275,7 @@ class StarboardCog(commands.Cog): await ctx.send(f"Star {id} deleted") -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)) bot.add_cog(StarboardCog(bot)) From dd8b3be19ac1c2e59e040ae863a0f84c9d1b6632 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 20:20:12 -0700 Subject: [PATCH 043/365] Migrate twitter, closes #104 --- jarvis/cogs/twitter.py | 169 ++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 93 deletions(-) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 65fe265..f69369f 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -4,18 +4,16 @@ 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.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, ) from jarvis.config import get_config @@ -25,10 +23,10 @@ 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( @@ -39,14 +37,15 @@ class TwitterCog(commands.Cog): self._guild_cache = {} self._channel_cache = {} - @loop(seconds=30) + @Task.create(trigger=IntervalTrigger(minutes=1)) async def _tweets(self) -> None: twitters = Twitter.objects(active=True) handles = Twitter.objects.distinct("handle") twitter_data = {} for handle in handles: try: - twitter_data[handle] = self.api.user_timeline(screen_name=handle) + data = await asyncio.to_thread(self.api.user_timeline, screen_name=handle) + twitter_data[handle] = data except Exception as e: logger.error(f"Error with fetching: {e}") for twitter in twitters: @@ -55,16 +54,15 @@ class TwitterCog(commands.Cog): filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle]) ) if tweets: + guild_id = twitter.guild + channel_id = twitter.channel tweets = sorted(tweets, key=lambda x: x.id) - if twitter.guild not in self._guild_cache: - self._guild_cache[twitter.guild] = await self.bot.fetch_guild(twitter.guild) + if guild_id not in self._guild_cache: + self._guild_cache[guild_id] = await self.bot.get_guild(guild_id) guild = self._guild_cache[twitter.guild] - if 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] + if channel_id not in self._channel_cache: + self._channel_cache[channel_id] = await guild.fetch_channel(channel_id) + channel = self._channel_cache[channel_id] for tweet in tweets: retweet = "retweeted_status" in tweet.__dict__ if retweet and not twitter.retweets: @@ -81,46 +79,37 @@ class TwitterCog(commands.Cog): 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", + @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") + @slash_option( + name="handle", description="Twitter account", option_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="channel", + description="Channel to post tweets to", + option_type=OptionTypes.CHANNEL, + required=True, + ) + @slash_option( + name="retweets", + description="Mirror re-tweets?", + option_type=OptionTypes.STRING, + required=False, 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"), - ], - ), + SlashCommandChoice(name="Yes", value="Yes"), + SlashCommandChoice(name="No", value="No"), ], ) - @admin_or_permissions(manage_guild=True) + @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) return - if not isinstance(channel, TextChannel): + if not isinstance(channel, GuildText): await ctx.send("Channel must be a text channel", hidden=True) return @@ -155,13 +144,9 @@ 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") + @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) @@ -170,14 +155,14 @@ class TwitterCog(commands.Cog): 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( + 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" @@ -186,19 +171,19 @@ class TwitterCog(commands.Cog): ) try: - context = await wait_for_component( - self.bot, + context = await self.bot.wait_for_component( check=lambda x: ctx.author.id == x.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( + + 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: @@ -207,25 +192,21 @@ class TwitterCog(commands.Cog): 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", + description="Mirror re-tweets?", + option_type=OptionTypes.STRING, + required=False, 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"), - ], - ), + 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: + @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: @@ -234,14 +215,14 @@ class TwitterCog(commands.Cog): 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( + 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" @@ -250,22 +231,24 @@ class TwitterCog(commands.Cog): ) try: - context = await wait_for_component( - self.bot, + 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( + + 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:" @@ -280,6 +263,6 @@ class TwitterCog(commands.Cog): 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)) From 2a7259d1b82be70a5ea8bc41fcba2f37904fbc7e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 20:21:48 -0700 Subject: [PATCH 044/365] Async API calls --- jarvis/cogs/twitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index f69369f..8f1ca59 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -114,7 +114,7 @@ class TwitterCog(Scale): return try: - latest_tweet = self.api.user_timeline(screen_name=handle, count=1)[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 From 05ee5d4fade30c9b00ca4a7ccf5f3dccfdac4e82 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 20:30:03 -0700 Subject: [PATCH 045/365] Fix component wait --- jarvis/cogs/rolegiver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index fbeaa4a..f4e076c 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -111,7 +111,7 @@ class RolegiverCog(Scale): context = await self.bot.wait_for_component( self.bot, check=lambda x: ctx.author.id == x.author.id, - message=message, + messages=message, timeout=60 * 1, ) for to_delete in context.context.values: From ed101de216fe73ab9f3418b2179d0f75a81c4120 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 20:35:13 -0700 Subject: [PATCH 046/365] Migrate verify, closes #105 --- jarvis/cogs/verify.py | 90 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index ada36fa..9c2af3e 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,30 +18,27 @@ 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) + action_row = spread_to_rows(*buttons, max_in_row=3) return action_row -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: @@ -53,40 +52,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 = 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: + 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.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`", - ) + 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") + + 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: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add VerifyCog to J.A.R.V.I.S.""" bot.add_cog(VerifyCog(bot)) From e4af3f413fced2ea266ecb1d3204ed4decf39a6a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 20:59:26 -0700 Subject: [PATCH 047/365] Migrate util, closes #113 --- jarvis/cogs/util.py | 262 ++++++++++++++++++++------------------------ 1 file changed, 120 insertions(+), 142 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 1696db7..af5e249 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -4,93 +4,84 @@ 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.embed import Color, 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="discord.py", 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") - 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", + option_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) @@ -109,57 +100,50 @@ class UtilCog(commands.Cog): 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", + option_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.display_avatar 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", + option_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=role.id), + EmbedField(name="Name", value=role.name), + EmbedField(name="Color", value=str(role.color)), + EmbedField(name="Mention", value=f"`{role.mention}`"), + EmbedField(name="Hoisted", value="Yes" if role.hoist else "No"), + EmbedField(name="Position", value=str(role.position)), + EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No"), + EmbedField(name="Member Count", value=role.members), ] - embed = build_embed( title="", description="", fields=fields, - color=str(role.color), + color=Color.from_hex(role.color), timestamp=role.created_at, ) embed.set_footer(text="Role Created") @@ -181,19 +165,17 @@ class UtilCog(commands.Cog): 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", + option_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 @@ -201,15 +183,15 @@ class UtilCog(commands.Cog): user_roles = sorted(user.roles, key=lambda x: -x.position) _ = user_roles.pop(-1) fields = [ - Field( + EmbedField( name="Joined", value=user.joined_at.strftime("%a, %b %-d, %Y %-I:%M %p"), ), - Field( + EmbedField( name="Registered", value=user.created_at.strftime("%a, %b %-d, %Y %-I:%M %p"), ), - 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,24 +202,27 @@ class UtilCog(commands.Cog): title="", description=user.mention, fields=fields, - color=str(user_roles[0].color) if user_roles else "#FF0000", + color=Color.from_hex(str(user_roles[0].color) if user_roles else "#3498db"), ) - embed.set_author(name=f"{user.name}#{user.discriminator}", icon_url=user.display_avatar) + embed.set_author( + name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar + ) embed.set_thumbnail(url=user.display_avatar) 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]`||" + f"{guild.owner.display_name}#{guild.owner.discriminator}" + if guild.owner + else "||`[redacted]`||" ) - region = guild.region categories = len(guild.categories) text_channels = len(guild.text_channels) voice_channels = len(guild.voice_channels) @@ -246,16 +231,15 @@ class UtilCog(commands.Cog): role_list = ", ".join(role.name for role in guild.roles) 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), + EmbedField(name="Channel Categories", value=categories), + EmbedField(name="Text Channels", value=text_channels), + EmbedField(name="Voice Channels", value=voice_channels), + EmbedField(name="Members", value=members), + EmbedField(name="Roles", value=roles), ] 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) @@ -265,35 +249,32 @@ class UtilCog(commands.Cog): 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], + scopes=[862402786116763668], + ) + @slash_option( + name="length", + description="Password length (default 32)", + option_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="chars", + description="Characters to include (default last option)", + option_type=OptionTypes.INTEGER, + required=False, 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), - ], - ), + 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) return @@ -312,14 +293,11 @@ class UtilCog(commands.Cog): hidden=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", option_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() @@ -334,6 +312,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)) From 487cc6b06b3031930ee8ba731ead9e508f46dfc5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 21:06:28 -0700 Subject: [PATCH 048/365] Fix options -> choices --- jarvis/cogs/admin/lock.py | 4 ++-- jarvis/cogs/admin/lockdown.py | 2 +- jarvis/cogs/gitlab.py | 4 ++-- jarvis/cogs/settings.py | 12 ++++++------ jarvis/cogs/twitter.py | 4 ++-- jarvis/cogs/util.py | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 75c6427..eaf4f13 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -49,7 +49,7 @@ class LockCog(CacheCog): @cog_ext.cog_slash( name="lock", description="Locks a channel", - options=[ + choices=[ create_option( name="reason", description="Lock Reason", @@ -105,7 +105,7 @@ class LockCog(CacheCog): @cog_ext.cog_slash( name="unlock", description="Unlocks a channel", - options=[ + choices=[ create_option( name="channel", description="Channel to lock", diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index dcf37ba..6f7055c 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -21,7 +21,7 @@ class LockdownCog(CacheCog): base="lockdown", name="start", description="Locks a server", - options=[ + choices=[ create_option( name="reason", description="Lockdown Reason", diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 33b278c..becc05f 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -268,7 +268,7 @@ class GitlabCog(CacheCog): description="State of issues to get", option_type=OptionTypes.STRING, required=False, - options=[ + choices=[ SlashCommandChoice(name="Open", value="opened"), SlashCommandChoice(name="Closed", value="closed"), SlashCommandChoice(name="All", value="all"), @@ -341,7 +341,7 @@ class GitlabCog(CacheCog): description="State of merge requests to get", option_type=OptionTypes.STRING, required=False, - options=[ + choices=[ SlashCommandChoice(name="Open", value="opened"), SlashCommandChoice(name="Closed", value="closed"), SlashCommandChoice(name="All", value="all"), diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 9102dbf..4bc8973 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -38,7 +38,7 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="modlog", description="Set modlog channel", - options=[ + choices=[ create_option( name="channel", description="Modlog channel", @@ -60,7 +60,7 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="userlog", description="Set userlog channel", - options=[ + choices=[ create_option( name="channel", description="Userlog channel", @@ -82,7 +82,7 @@ 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)", @@ -102,7 +102,7 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="verified", description="Set verified role", - options=[ + choices=[ create_option( name="role", description="verified role", @@ -122,7 +122,7 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="unverified", description="Set unverified role", - options=[ + choices=[ create_option( name="role", description="Unverified role", @@ -142,7 +142,7 @@ class SettingsCog(commands.Cog): subcommand_group="set", name="noinvite", description="Set if invite deletion should happen", - options=[ + choices=[ create_option( name="active", description="Active?", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 8f1ca59..db40aac 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -94,7 +94,7 @@ class TwitterCog(Scale): description="Mirror re-tweets?", option_type=OptionTypes.STRING, required=False, - options=[ + choices=[ SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="No", value="No"), ], @@ -200,7 +200,7 @@ class TwitterCog(Scale): description="Mirror re-tweets?", option_type=OptionTypes.STRING, required=False, - options=[ + choices=[ SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="No", value="No"), ], diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index af5e249..eb829bc 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -266,7 +266,7 @@ class UtilCog(Scale): description="Characters to include (default last option)", option_type=OptionTypes.INTEGER, required=False, - options=[ + choices=[ SlashCommandChoice(name="A-Za-z", value=0), SlashCommandChoice(name="A-Fa-f0-9", value=1), SlashCommandChoice(name="A-Za-z0-9", value=2), From bc94cd716b9370f18857d99dfe815ea4a22f9c33 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 21:16:04 -0700 Subject: [PATCH 049/365] Migrate dev, closes #116 --- jarvis/cogs/dev.py | 200 ++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 110 deletions(-) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 7df8699..2997901 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -8,12 +8,18 @@ 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} @@ -55,33 +61,25 @@ def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str: 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", + option_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", + option_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", @@ -96,33 +94,28 @@ 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", + option_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", + option_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) @@ -141,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}`") @@ -183,56 +176,46 @@ 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", + option_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", + option_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", + option_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", + option_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") @@ -243,24 +226,21 @@ class DevCog(commands.Cog): ) 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: + @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)) From e79e298c11228ed1615bb84cbb816eba8cec4fff Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 21:28:51 -0700 Subject: [PATCH 050/365] Migrate tasks, closes #117 --- jarvis/tasks/unban.py | 23 +++++++++++++++-------- jarvis/tasks/unlock.py | 42 +++++++++++++++++++++++++++--------------- jarvis/tasks/unwarn.py | 15 +++++++++++---- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index 620225f..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,19 +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) + 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( @@ -36,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/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) From 61e4d4d4973a91d8edb99df3ac99094adab08082 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 3 Feb 2022 21:30:32 -0700 Subject: [PATCH 051/365] Make sync be false by default --- jarvis/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/config.py b/jarvis/config.py index 21b56a7..041b894 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -29,7 +29,7 @@ class Config(object): logo: str, mongo: dict, urls: dict, - sync: bool, + sync: bool = False, log_level: str = "WARNING", cogs: list = None, events: bool = True, From 490c8fc142f1690c5fee51547162b24b749ccec9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:47:56 -0700 Subject: [PATCH 052/365] Remove guild event handler, fix other handlers --- jarvis/events/guild.py | 36 ------------------------------------ jarvis/events/member.py | 3 ++- jarvis/events/message.py | 24 +++++++++++++----------- 3 files changed, 15 insertions(+), 48 deletions(-) delete mode 100644 jarvis/events/guild.py diff --git a/jarvis/events/guild.py b/jarvis/events/guild.py deleted file mode 100644 index 119b815..0000000 --- a/jarvis/events/guild.py +++ /dev/null @@ -1,36 +0,0 @@ -"""J.A.R.V.I.S. guild event handler.""" -import asyncio - -from dis_snek import Snake -from dis_snek.models.discord.guild import Guild - -from jarvis.db.models import Setting -from jarvis.utils import find - - -class GuildEventHandler(object): - """J.A.R.V.I.S. guild event handler.""" - - def __init__(self, bot: Snake): - 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 f849a9c..ee1043f 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. Member event handler.""" -from dis_snek import Snake +from dis_snek import Snake, listen from dis_snek.models.discord.user import Member from jarvis.db.models import Mute, Setting @@ -12,6 +12,7 @@ class MemberEventHandler(object): 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 74b68e6..ceb73ef 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -1,14 +1,14 @@ """J.A.R.V.I.S. Message event handler.""" import re -from dis_snek import Snake +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, find -from jarvis.utils.field import Field invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 @@ -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( @@ -113,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", @@ -178,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( @@ -198,6 +198,7 @@ class MessageEventHandler(object): ) 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: @@ -207,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: From 35104cc40bf83f365b731bc15c85ff66f22effa9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:48:22 -0700 Subject: [PATCH 053/365] Disable lock and lockdown --- jarvis/cogs/admin/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index c3fad57..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 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: 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) From fa4785f0737976aa2364837738cbf9c7b0103325 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:49:43 -0700 Subject: [PATCH 054/365] Move reminder and twitter tasks to tasks/ --- jarvis/tasks/__init__.py | 3 +- jarvis/tasks/reminder.py | 46 +++++++++++++++++++++++++++ jarvis/tasks/twitter.py | 67 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 jarvis/tasks/reminder.py create mode 100644 jarvis/tasks/twitter.py diff --git a/jarvis/tasks/__init__.py b/jarvis/tasks/__init__.py index e5cd73c..9825337 100644 --- a/jarvis/tasks/__init__.py +++ b/jarvis/tasks/__init__.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. background task handlers.""" -from jarvis.tasks import unban, unlock, unwarn +from jarvis.tasks import twitter, unban, unlock, unwarn def init() -> None: @@ -7,3 +7,4 @@ def init() -> None: unban.unban.start() unlock.unlock.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..a4b1601 --- /dev/null +++ b/jarvis/tasks/reminder.py @@ -0,0 +1,46 @@ +"""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, + ) + embed.set_thumbnail(url=user.display_avatar) + 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..fe1a9c1 --- /dev/null +++ b/jarvis/tasks/twitter.py @@ -0,0 +1,67 @@ +"""J.A.R.V.I.S. twitter background task handler.""" +import logging +from asyncio import to_thread + +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 = dict() + channel_cache = dict() + twitters = Twitter.objects(active=True) + handles = Twitter.objects.distinct("handle") + twitter_data = dict() + for handle in handles: + try: + data = __api.user_timeline(screen_name=handle) + twitter_data[handle] = data + except Exception as e: + logger.error(f"Error with fetching: {e}") + for twitter in twitters: + try: + tweets = list(filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle])) + if tweets: + guild_id = twitter.guild + channel_id = twitter.channel + tweets = sorted(tweets, key=lambda x: x.id) + if guild_id not in 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) From 88d596cad7286257b51530a3241e99ee76434aa1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:53:06 -0700 Subject: [PATCH 055/365] Fix common issues in cogs (ephemeral, opt_type, check) --- jarvis/cogs/admin/ban.py | 43 +++++++------- jarvis/cogs/admin/kick.py | 15 +++-- jarvis/cogs/admin/mute.py | 35 ++++++----- jarvis/cogs/admin/purge.py | 35 +++++------ jarvis/cogs/admin/roleping.py | 73 +++++++++++------------ jarvis/cogs/admin/warning.py | 29 +++++---- jarvis/cogs/autoreact.py | 31 +++++----- jarvis/cogs/ctc2.py | 16 ++--- jarvis/cogs/dbrand.py | 4 +- jarvis/cogs/dev.py | 24 ++++---- jarvis/cogs/error.py | 8 +-- jarvis/cogs/gitlab.py | 30 +++++----- jarvis/cogs/image.py | 2 +- jarvis/cogs/jokes.py | 6 +- jarvis/cogs/modlog/__init__.py | 6 +- jarvis/cogs/owner.py | 5 +- jarvis/cogs/remindme.py | 70 ++++++---------------- jarvis/cogs/rolegiver.py | 34 +++++------ jarvis/cogs/starboard.py | 55 +++++++++-------- jarvis/cogs/twitter.py | 79 +++++-------------------- jarvis/cogs/util.py | 104 +++++++++++++++++---------------- jarvis/cogs/verify.py | 2 +- 22 files changed, 310 insertions(+), 396 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 2859530..263239b 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -12,6 +12,7 @@ from dis_snek.models.snek.application_commands import ( 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, find @@ -90,16 +91,14 @@ class BanCog(CacheCog): await ctx.send(embed=embed) @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="user", description="User to ban", option_type=OptionTypes.USER, required=True - ) - @slash_option( - name="reason", description="Ban reason", option_type=OptionTypes.STRING, required=True + name="reason", description="Ban reason", opt_type=OptionTypes.STRING, required=True ) @slash_option( name="btype", description="Ban type", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, choices=[ SlashCommandChoice(name="Permanent", value="perm"), @@ -107,7 +106,7 @@ class BanCog(CacheCog): SlashCommandChoice(name="Soft", value="soft"), ], ) - @admin_or_permissions(Permissions.BAN_MEMBERS) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _ban( self, ctx: InteractionContext, @@ -117,19 +116,19 @@ class BanCog(CacheCog): 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 await ctx.defer() @@ -167,7 +166,7 @@ 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": @@ -184,12 +183,12 @@ class BanCog(CacheCog): @slash_command(name="unban", description="Unban a user") @slash_option( - name="user", description="User to unban", option_type=OptionTypes.STRING, required=True + name="user", description="User to unban", opt_type=OptionTypes.STRING, required=True ) @slash_option( - name="reason", description="Unban reason", option_type=OptionTypes.STRING, required=True + name="reason", description="Unban reason", opt_type=OptionTypes.STRING, required=True ) - @admin_or_permissions(Permissions.BAN_MEMBERS) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _unban( self, ctx: InteractionContext, @@ -197,7 +196,7 @@ class BanCog(CacheCog): 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 @@ -255,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) @@ -284,7 +283,7 @@ class BanCog(CacheCog): @slash_option( name="btype", description="Ban type", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, choices=[ SlashCommandChoice(name="All", value=0), @@ -296,19 +295,19 @@ class BanCog(CacheCog): @slash_option( name="active", description="Active bans", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], ) - @admin_or_permissions(Permissions.BAN_MEMBERS) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None: 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"] diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index b7b0a6d..28aaf79 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -7,6 +7,7 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from dis_snek.models.snek.command import check from jarvis.db.models import Kick from jarvis.utils import build_embed @@ -17,22 +18,20 @@ class KickCog(Scale): """J.A.R.V.I.S. KickCog.""" @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="user", description="User to kick", option_type=OptionTypes.USER, required=True + name="reason", description="Kick reason", opt_type=OptionTypes.STRING, required=True ) - @slash_option( - name="reason", description="Kick reason", option_type=OptionTypes.STRING, required=True - ) - @admin_or_permissions(Permissions.BAN_MEMBERS) + @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 guild_name = ctx.guild.name embed = build_embed( diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index b406fd4..8c36fbb 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -10,6 +10,7 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from dis_snek.models.snek.command import check from jarvis.db.models import Mute from jarvis.utils import build_embed @@ -23,25 +24,23 @@ class MuteCog(Scale): self.bot = bot @slash_command(name="mute", description="Mute a user") - @slash_option( - name="user", description="User to mute", option_type=OptionTypes.USER, required=True - ) + @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( name="reason", description="Reason for mute", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) @slash_option( name="time", description="Duration of mute, default 1", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) @slash_option( name="scale", description="Time scale, default Hour(s)", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, choices=[ SlashCommandChoice(name="Minute(s)", value=1), @@ -50,26 +49,28 @@ class MuteCog(Scale): SlashCommandChoice(name="Week(s)", value=604800), ], ) - @admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + @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 # 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)", hidden=True) + await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) return await user.timeout(communication_disabled_until=duration, reason=reason) @@ -97,17 +98,19 @@ class MuteCog(Scale): @slash_command(name="unmute", description="Unmute a user") @slash_option( - name="user", description="User to unmute", option_type=OptionTypes.USER, required=True + name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True ) - @admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + @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", hidden=True) + await ctx.send("User is not muted", ephemeral=True) return embed = build_embed( diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 3310416..e86cb1a 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -6,6 +6,7 @@ from dis_snek.models.snek.application_commands import ( 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 @@ -21,13 +22,13 @@ class PurgeCog(Scale): @slash_option( name="amount", description="Amount of messages to purge, default 10", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) - @admin_or_permissions(Permissions.MANAGE_MESSAGES) + @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) async def _purge(self, ctx: InteractionContext, amount: int = 10) -> None: 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 @@ -48,31 +49,31 @@ class PurgeCog(Scale): @slash_option( name="channel", description="Channel to autopurge", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) @slash_option( name="delay", description="Seconds to keep message before purge, default 30", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) - @admin_or_permissions(Permissions.MANAGE_MESSAGES) + @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", hidden=True) + 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, @@ -88,14 +89,14 @@ class PurgeCog(Scale): @slash_option( name="channel", description="Channel to remove from autopurge", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) - @admin_or_permissions(Permissions.MANAGE_MESSAGES) + @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None: 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}.") @@ -108,22 +109,22 @@ class PurgeCog(Scale): @slash_option( name="channel", description="Channel to update", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) @slash_option( name="delay", description="New time to save", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=True, ) - @admin_or_permissions(Permissions.MANAGE_MESSAGES) + @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 14d2bbd..1dc1561 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -11,6 +11,7 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from dis_snek.models.snek.command import check from jarvis.db.models import Roleping from jarvis.utils import build_embed @@ -27,14 +28,12 @@ class RolepingCog(CacheCog): @slash_command( name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" ) - @slash_option( - name="role", description="Role to add", option_type=OptionTypes.ROLE, required=True - ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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, @@ -47,13 +46,13 @@ class RolepingCog(CacheCog): @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") @slash_option( - name="role", description="Role to remove", option_type=OptionTypes.ROLE, required=True + name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None: 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() @@ -63,16 +62,16 @@ class RolepingCog(CacheCog): 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 = [] @@ -137,29 +136,27 @@ class RolepingCog(CacheCog): sub_cmd_name="user", sub_cmd_description="Add a user as a bypass to a roleping", ) + @slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True) @slash_option( - name="user", description="User to add", option_type=OptionTypes.USER, required=True + name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @slash_option( - name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True - ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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 @@ -168,7 +165,7 @@ 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 @@ -182,28 +179,26 @@ class RolepingCog(CacheCog): sub_cmd_name="role", description="Add a role as a bypass to roleping", ) + @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @slash_option( - name="role", description="Role to add", option_type=OptionTypes.ROLE, required=True + name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @slash_option( - name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True - ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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, + ephemeral=True, ) return @@ -220,22 +215,22 @@ class RolepingCog(CacheCog): sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="user", description="User to remove", option_type=OptionTypes.USER, required=True + name="user", description="User to remove", opt_type=OptionTypes.USER, required=True ) @slash_option( - name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True + name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_restore_user( 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) @@ -249,29 +244,29 @@ class RolepingCog(CacheCog): description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="role", description="Role to remove", option_type=OptionTypes.ROLE, required=True + name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) @slash_option( - name="rping", description="Rolepinged role", option_type=OptionTypes.ROLE, required=True + name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @admin_or_permissions(manage_guild=True) + @check(admin_or_permissions(manage_guild=True)) async def _roleping_restore_role( 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, + ephemeral=True, ) return diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index f81443f..aa75f70 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -10,6 +10,7 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from dis_snek.models.snek.command import check from jarvis.db.models import Warning from jarvis.utils import build_embed @@ -25,33 +26,31 @@ class WarningCog(CacheCog): super().__init__(bot) @slash_command(name="warn", description="Warn a user") - @slash_option( - name="user", description="User to warn", option_type=OptionTypes.USER, required=True - ) + @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( name="reason", description="Reason for warning", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) @slash_option( name="duration", description="Duration of warning in hours, default 24", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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( @@ -77,28 +76,26 @@ class WarningCog(CacheCog): await ctx.send(embed=embed) @slash_command(name="warnings", description="Get count of user warnings") - @slash_option( - name="user", description="User to view", option_type=OptionTypes.USER, required=True - ) + @slash_option(name="user", description="User to view", opt_type=OptionTypes.USER, required=True) @slash_option( name="active", description="View active only", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, choices=[ SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0), ], ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: 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( diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 289b3d8..78d403b 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -9,6 +9,7 @@ from dis_snek.models.snek.application_commands import ( 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 @@ -70,13 +71,13 @@ class AutoReactCog(Scale): @slash_option( name="channel", description="Autoreact channel to add emote to", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) @slash_option( - name="emote", description="Emote to add", option_type=OptionTypes.STRING, required=True + name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None: await ctx.defer() custom_emoji = self.custom_emote.match(emote) @@ -84,13 +85,13 @@ class AutoReactCog(Scale): 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 autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() if not autoreact: @@ -99,13 +100,13 @@ class AutoReactCog(Scale): if emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", - hidden=True, + ephemeral=True, ) return 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 autoreact.reactions.append(emote) @@ -120,16 +121,16 @@ class AutoReactCog(Scale): @slash_option( name="channel", description="Autoreact channel to remove emote from", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) @slash_option( name="emote", description="Emote to remove (use all to delete)", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _autoreact_remove( self, ctx: InteractionContext, channel: GuildText, emote: str ) -> None: @@ -137,7 +138,7 @@ class AutoReactCog(Scale): if not autoreact: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} {emote}", - hidden=True, + ephemeral=True, ) return if emote.lower() == "all": @@ -146,7 +147,7 @@ class AutoReactCog(Scale): elif emote not in autoreact.reactions: await ctx.send( f"{emote} not used in {channel.mention} autoreactions.", - hidden=True, + ephemeral=True, ) return else: @@ -164,7 +165,7 @@ class AutoReactCog(Scale): @slash_option( name="channel", description="Autoreact channel to list", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None: @@ -172,7 +173,7 @@ class AutoReactCog(Scale): if not exists: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} ", - hidden=True, + ephemeral=True, ) return message = "" @@ -187,4 +188,4 @@ class AutoReactCog(Scale): 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 72687eb..d509f0f 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -56,7 +56,7 @@ class CTCCog(CacheCog): "Listen here, dipshit. Don't be like <@256110768724901889>. " "Make your guesses < 800 characters." ), - hidden=True, + ephemeral=True, ) return elif not valid.fullmatch(guess): @@ -65,18 +65,18 @@ class CTCCog(CacheCog): "Listen here, dipshit. Don't be like <@256110768724901889>. " "Make your guesses *readable*." ), - hidden=True, + 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 @@ -84,7 +84,7 @@ 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() @slash_command( @@ -97,10 +97,10 @@ class CTCCog(CacheCog): 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 @@ -153,4 +153,4 @@ class CTCCog(CacheCog): 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 4858202..a6f0f41 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -156,7 +156,7 @@ class DbrandCog(Scale): @slash_option( name="search", description="Country search query (2 character code, country name, flag emoji)", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) @@ -266,4 +266,4 @@ class DbrandCog(Scale): 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 2997901..98f2db2 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -68,14 +68,14 @@ class DevCog(Scale): @slash_option( name="method", description="Hash method", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, choices=[SlashCommandChoice(name=x, value=x) for x in supported_hashes], ) @slash_option( name="data", description="Data to hash", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) @@ -83,7 +83,7 @@ class DevCog(Scale): if not data: await ctx.send( "No data to hash", - hidden=True, + ephemeral=True, ) return text = True @@ -105,20 +105,20 @@ class DevCog(Scale): @slash_option( name="version", description="UUID version", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, choices=[SlashCommandChoice(name=x, value=x) for x in ["3", "4", "5"]], ) @slash_option( name="data", description="Data for UUID version 3,5", - option_type=OptionTypes.STRING, + 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()}`") @@ -180,14 +180,14 @@ class DevCog(Scale): @slash_option( name="method", description="Encode method", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], ) @slash_option( name="data", description="Data to encode", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: @@ -205,14 +205,14 @@ class DevCog(Scale): @slash_option( name="method", description="Decode method", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, choices=[SlashCommandChoice(name=x, value=x) for x in base64_methods], ) @slash_option( name="data", description="Data to encode", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None: @@ -222,7 +222,7 @@ class DevCog(Scale): if invites.search(decoded): await ctx.send( "Please don't use this to bypass invite restrictions", - hidden=True, + ephemeral=True, ) return fields = [ @@ -243,4 +243,4 @@ class DevCog(Scale): 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 3715d03..d245b0c 100644 --- a/jarvis/cogs/error.py +++ b/jarvis/cogs/error.py @@ -33,19 +33,19 @@ class ErrorHandlerCog(commands.Cog): 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) + 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, + 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) @@ -53,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 becc05f..6c769b0 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -32,12 +32,12 @@ class GitlabCog(CacheCog): @slash_command( name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids ) - @slash_option(name="id", description="Issue ID", option_type=OptionTypes.INTEGER, required=True) + @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) async def _issue(self, ctx: InteractionContext, id: int) -> None: 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: @@ -101,13 +101,13 @@ class GitlabCog(CacheCog): scopes=guild_ids, ) @slash_option( - name="id", description="Milestone ID", option_type=OptionTypes.INTEGER, required=True + name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True ) async def _milestone(self, ctx: InteractionContext, id: int) -> None: 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( @@ -157,13 +157,13 @@ class GitlabCog(CacheCog): scopes=guild_ids, ) @slash_option( - name="id", description="Merge Request ID", option_type=OptionTypes.INTEGER, required=True + name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True ) async def _mergerequest(self, ctx: InteractionContext, id: int) -> None: 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: @@ -266,7 +266,7 @@ class GitlabCog(CacheCog): @slash_option( name="state", description="State of issues to get", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=False, choices=[ SlashCommandChoice(name="Open", value="opened"), @@ -277,10 +277,10 @@ class GitlabCog(CacheCog): async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None: exists = self.check_cache(ctx, state=state) if exists: - await ctx.defer(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() @@ -339,7 +339,7 @@ class GitlabCog(CacheCog): @slash_option( name="state", description="State of merge requests to get", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=False, choices=[ SlashCommandChoice(name="Open", value="opened"), @@ -350,10 +350,10 @@ class GitlabCog(CacheCog): async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None: exists = self.check_cache(ctx, state=state) if exists: - await ctx.defer(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() @@ -414,10 +414,10 @@ class GitlabCog(CacheCog): 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() @@ -464,4 +464,4 @@ class GitlabCog(CacheCog): 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 45fc0c8..83aff59 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -105,4 +105,4 @@ class ImageCog(Scale): 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 2512695..0bf3fcc 100644 --- a/jarvis/cogs/jokes.py +++ b/jarvis/cogs/jokes.py @@ -34,7 +34,7 @@ class JokeCog(Scale): name="joke", description="Hear a joke", ) - @slash_option(name="id", description="Joke ID", required=False, option_type=OptionTypes.INTEGER) + @slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER) @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10) async def _joke(self, ctx: InteractionContext, id: str = None) -> None: """Get a joke from the database.""" @@ -57,7 +57,7 @@ class JokeCog(Scale): 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: @@ -118,4 +118,4 @@ class JokeCog(Scale): 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/owner.py b/jarvis/cogs/owner.py index 0e55bde..c00118e 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -2,6 +2,7 @@ from dis_snek import MessageContext, Scale, Snake, message_command from dis_snek.models.discord.user import User from dis_snek.models.snek.checks import is_owner +from dis_snek.models.snek.command import check from jarvis.config import reload_config from jarvis.db.models import Config @@ -19,7 +20,7 @@ class OwnerCog(Scale): self.admins = Config.objects(key="admins").first() @message_command(name="addadmin") - @is_owner() + @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.") @@ -43,4 +44,4 @@ class OwnerCog(Scale): 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 a5c7ccf..e1973ca 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -6,8 +6,6 @@ from typing import List, Optional from bson import ObjectId from dis_snek import InteractionContext, Snake -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.snek.application_commands import ( @@ -32,34 +30,33 @@ class RemindmeCog(CacheCog): def __init__(self, bot: Snake): super().__init__(bot) - self._remind.start() @slash_command(name="remindme", description="Set a reminder") @slash_option( name="message", description="What to remind you of?", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=True, ) @slash_option( name="weeks", description="Number of weeks?", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) @slash_option( - name="days", description="Number of days?", option_type=OptionTypes.INTEGER, required=False + name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False ) @slash_option( name="hours", description="Number of hours?", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) @slash_option( name="minutes", description="Number of minutes?", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) async def _remindme( @@ -72,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) @@ -94,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() @@ -114,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 @@ -187,15 +184,15 @@ class RemindmeCog(CacheCog): 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) @@ -206,7 +203,7 @@ class RemindmeCog(CacheCog): 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 = [] @@ -279,36 +276,7 @@ class RemindmeCog(CacheCog): component["disabled"] = True await message.edit(components=components) - @Task.create(trigger=IntervalTrigger(seconds=15)) - async def _remind(self) -> None: - reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30)) - for reminder in reminders: - if reminder.remind_at <= datetime.utcnow(): - user = await self.bot.fetch_user(reminder.user) - if not user: - reminder.delete() - continue - embed = build_embed( - title="You have a reminder", - description=reminder.message, - fields=[], - ) - embed.set_author( - name=user.name + "#" + user.discriminator, - icon_url=user.display_avatar, - ) - embed.set_thumbnail(url=user.display_avatar) - try: - await user.send(embed=embed) - except Exception: - guild = self.bot.fetch_guild(reminder.guild) - channel = guild.get_channel(reminder.channel) if guild else None - if channel: - await channel.send(f"{user.mention}", embed=embed) - finally: - reminder.delete() - def setup(bot: Snake) -> None: """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 f4e076c..b8e6b64 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -2,7 +2,6 @@ import asyncio from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils import get from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -11,11 +10,11 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) -from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.command import check, cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis.db.models import Rolegiver -from jarvis.utils import build_embed +from jarvis.utils import build_embed, get from jarvis.utils.permissions import admin_or_permissions @@ -28,21 +27,19 @@ class RolegiverCog(Scale): @slash_command( name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver" ) - @slash_option( - name="role", description="Role to add", optin_type=OptionTypes.ROLE, required=True - ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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) @@ -84,11 +81,11 @@ class RolegiverCog(Scale): @slash_command( name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver" ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_remove(self, ctx: InteractionContext) -> None: 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 = [] @@ -168,7 +165,7 @@ class RolegiverCog(Scale): 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 = [] @@ -204,7 +201,7 @@ class RolegiverCog(Scale): 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 = [] @@ -283,10 +280,10 @@ class RolegiverCog(Scale): 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 not any(x.id in setting.roles for x in user_roles): - await ctx.send("You have no rolegiver roles", hidden=True) + await ctx.send("You have no rolegiver roles", ephemeral=True) return valid = list(filter(lambda x: x.id in setting.roles, user_roles)) @@ -361,11 +358,11 @@ class RolegiverCog(Scale): @slash_command( name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: setting = Rolegiver.objects(guild=ctx.guild.id).first() if not setting or not setting.roles: - await ctx.send("Rolegiver has no roles", hidden=True) + await ctx.send("Rolegiver has no roles", ephemeral=True) guild_role_ids = [r.id for r in ctx.guild.roles] for role_id in setting.roles: if role_id not in guild_role_ids: @@ -377,5 +374,4 @@ class RolegiverCog(Scale): def setup(bot: Snake) -> None: """Add RolegiverCog to J.A.R.V.I.S.""" - bot.add_cog(RolegiverCog(bot)) - bot.add_cog(RolegiverCog(bot)) + RolegiverCog(bot) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 81747bb..2f39d5f 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,6 +1,5 @@ """J.A.R.V.I.S. Starboard Cog.""" from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.message import Message @@ -11,9 +10,10 @@ from dis_snek.models.snek.application_commands import ( 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 = [ @@ -32,7 +32,7 @@ class StarboardCog(Scale): self.bot = bot @slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards") - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _list(self, ctx: InteractionContext) -> None: starboards = Starboard.objects(guild=ctx.guild.id) if starboards != []: @@ -49,29 +49,29 @@ class StarboardCog(Scale): @slash_option( name="channel", description="Starboard channel", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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, GuildText): - await ctx.send("Channel must be a GuildText", hidden=True) + 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( @@ -87,33 +87,33 @@ class StarboardCog(Scale): @slash_option( name="channel", description="Starboard channel", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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}.", ephemeral=True) else: - await ctx.send(f"Starboard not found in {channel.mention}.", hidden=True) + await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True) - @context_menu(name="Star Message", target=CommandTypes.MESSAGE) + @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add.invoke(ctx, ctx.target_message) @slash_command(name="star", sub_cmd_name="add", description="Star a message") @slash_option( - name="message", description="Message to star", option_type=OptionTypes.STRING, required=True + name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True ) @slash_option( name="channel", description="Channel that has the message, not required if used in same channel", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=False, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _star_add( self, ctx: InteractionContext, @@ -124,7 +124,7 @@ class StarboardCog(Scale): 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() @@ -135,7 +135,7 @@ class StarboardCog(Scale): message = await channel.get_message(int(message)) if not message: - await ctx.send("Message not found", hidden=True) + await ctx.send("Message not found", ephemeral=True) return channel_list = [] @@ -174,7 +174,7 @@ class StarboardCog(Scale): if exists: await ctx.send( f"Message already sent to Starboard {starboard.mention}", - hidden=True, + ephemeral=True, ) return @@ -229,15 +229,15 @@ class StarboardCog(Scale): @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") @slash_option( - name="message", description="Star to delete", option_type=OptionTypes.INTEGER, required=True + name="message", description="Star to delete", opt_type=OptionTypes.INTEGER, required=True ) @slash_option( name="starboard", description="Starboard to delete star from", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _star_delete( self, ctx: InteractionContext, @@ -245,13 +245,13 @@ class StarboardCog(Scale): starboard: GuildText, ) -> None: if not isinstance(starboard, GuildText): - await ctx.send("Channel must be a GuildText channel", hidden=True) + await ctx.send("Channel must be a GuildText channel", ephemeral=True) return 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 @@ -262,7 +262,7 @@ class StarboardCog(Scale): 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) @@ -277,5 +277,4 @@ class StarboardCog(Scale): def setup(bot: Snake) -> None: """Add StarboardCog to J.A.R.V.I.S.""" - bot.add_cog(StarboardCog(bot)) - bot.add_cog(StarboardCog(bot)) + StarboardCog(bot) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index db40aac..33ed8e6 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,12 +1,9 @@ """J.A.R.V.I.S. Twitter Cog.""" import asyncio -import logging import tweepy from bson import ObjectId from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( @@ -15,13 +12,12 @@ from dis_snek.models.snek.application_commands import ( 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(Scale): """J.A.R.V.I.S. Twitter Cog.""" @@ -33,102 +29,59 @@ class TwitterCog(Scale): config.twitter["consumer_key"], config.twitter["consumer_secret"] ) self.api = tweepy.API(auth) - self._tweets.start() self._guild_cache = {} self._channel_cache = {} - @Task.create(trigger=IntervalTrigger(minutes=1)) - async def _tweets(self) -> None: - twitters = Twitter.objects(active=True) - handles = Twitter.objects.distinct("handle") - twitter_data = {} - for handle in handles: - try: - data = await asyncio.to_thread(self.api.user_timeline, screen_name=handle) - twitter_data[handle] = data - except Exception as e: - logger.error(f"Error with fetching: {e}") - for twitter in twitters: - try: - tweets = list( - filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle]) - ) - if tweets: - guild_id = twitter.guild - channel_id = twitter.channel - tweets = sorted(tweets, key=lambda x: x.id) - if guild_id not in self._guild_cache: - self._guild_cache[guild_id] = await self.bot.get_guild(guild_id) - guild = self._guild_cache[twitter.guild] - if channel_id not in self._channel_cache: - self._channel_cache[channel_id] = await guild.fetch_channel(channel_id) - channel = self._channel_cache[channel_id] - for tweet in tweets: - retweet = "retweeted_status" in tweet.__dict__ - if retweet and not twitter.retweets: - continue - timestamp = int(tweet.created_at.timestamp()) - url = f"https://twitter.com/{twitter.handle}/status/{tweet.id}" - verb = "re" if retweet else "" - await channel.send( - f"`@{twitter.handle}` {verb}tweeted this at : {url}" - ) - newest = max(tweets, key=lambda x: x.id) - twitter.last_tweet = newest.id - twitter.save() - except Exception as e: - logger.error(f"Error with tweets: {e}") - @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") @slash_option( - name="handle", description="Twitter account", option_type=OptionTypes.STRING, required=True + name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True ) @slash_option( name="channel", description="Channel to post tweets to", - option_type=OptionTypes.CHANNEL, + opt_type=OptionTypes.CHANNEL, required=True, ) @slash_option( name="retweets", description="Mirror re-tweets?", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=False, choices=[ SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="No", value="No"), ], ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_follow( 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, GuildText): - await ctx.send("Channel must be a text channel", hidden=True) + await ctx.send("Channel must be a text channel", ephemeral=True) return try: 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 + "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) if exists: - await ctx.send("Twitter handle already being followed in this guild", hidden=True) + await ctx.send("Twitter handle already being followed in this guild", ephemeral=True) return t = Twitter( @@ -145,11 +98,11 @@ class TwitterCog(Scale): await ctx.send(f"Now following `@{handle}` in {channel.mention}") @slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts") - @admin_or_permissions(Permissions.MANAGE_GUILD) + @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 = [] @@ -198,19 +151,19 @@ class TwitterCog(Scale): @slash_option( name="retweets", description="Mirror re-tweets?", - option_type=OptionTypes.STRING, + opt_type=OptionTypes.STRING, required=False, choices=[ SlashCommandChoice(name="Yes", value="Yes"), SlashCommandChoice(name="No", value="No"), ], ) - @admin_or_permissions(Permissions.MANAGE_GUILD) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: 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 = [] @@ -265,4 +218,4 @@ class TwitterCog(Scale): 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 eb829bc..0d18689 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -6,7 +6,8 @@ from io import BytesIO import numpy as np from dis_snek import InteractionContext, Scale, Snake, const -from dis_snek.models.discord.embed import Color, EmbedField +from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.role import Role @@ -41,7 +42,7 @@ class UtilCog(Scale): self.bot = bot self.config = get_config() - @slash_command(name="Status", description="Retrieve J.A.R.V.I.S. status") + @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _status(self, ctx: InteractionContext) -> None: title = "J.A.R.V.I.S. Status" @@ -49,7 +50,7 @@ class UtilCog(Scale): color = "#3498db" fields = [] - fields.append(EmbedField(name="discord.py", value=const.__version__)) + fields.append(EmbedField(name="dis-snek", value=const.__version__)) fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) embed = build_embed(title=title, description=desc, fields=fields, color=color) @@ -64,7 +65,7 @@ class UtilCog(Scale): 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) @slash_command(name="rchk", description="Robot Camo HK416") @@ -78,13 +79,13 @@ class UtilCog(Scale): @slash_option( name="text", description="Text to camo-ify", - option_type=OptionTypes.STRING, + 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 == " ": @@ -96,7 +97,7 @@ class UtilCog(Scale): else: to_send += f"<:{names[id]}:{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) @@ -104,7 +105,7 @@ class UtilCog(Scale): @slash_option( name="user", description="User to view avatar of", - option_type=OptionTypes.USER, + opt_type=OptionTypes.USER, required=False, ) @cooldown(bucket=Buckets.USER, rate=1, interval=5) @@ -112,7 +113,7 @@ class UtilCog(Scale): if not user: user = ctx.author - avatar = user.display_avatar + avatar = user.display_avatar.url embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE") embed.set_image(url=avatar) embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar) @@ -125,25 +126,25 @@ class UtilCog(Scale): @slash_option( name="role", description="Role to get info of", - option_type=OptionTypes.ROLE, + opt_type=OptionTypes.ROLE, required=True, ) async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None: fields = [ - EmbedField(name="ID", value=role.id), - EmbedField(name="Name", value=role.name), - EmbedField(name="Color", value=str(role.color)), - EmbedField(name="Mention", value=f"`{role.mention}`"), - EmbedField(name="Hoisted", value="Yes" if role.hoist else "No"), - EmbedField(name="Position", value=str(role.position)), - EmbedField(name="Mentionable", value="Yes" if role.mentionable else "No"), - EmbedField(name="Member Count", value=role.members), + 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=Color.from_hex(role.color), + color=role.color, timestamp=role.created_at, ) embed.set_footer(text="Role Created") @@ -154,14 +155,14 @@ class UtilCog(Scale): 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) @@ -172,7 +173,7 @@ class UtilCog(Scale): @slash_option( name="user", description="User to get info of", - option_type=OptionTypes.USER, + opt_type=OptionTypes.USER, required=False, ) async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: @@ -181,15 +182,15 @@ class UtilCog(Scale): user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) - _ = user_roles.pop(-1) + fields = [ EmbedField( name="Joined", - value=user.joined_at.strftime("%a, %b %-d, %Y %-I:%M %p"), + value=user.joined_at.strftime("%a, %b %#d, %Y %#I:%M %p"), ), EmbedField( name="Registered", - value=user.created_at.strftime("%a, %b %-d, %Y %-I:%M %p"), + value=user.created_at.strftime("%a, %b %#d, %Y %#I:%M %p"), ), EmbedField( name=f"Roles [{len(user_roles)}]", @@ -202,13 +203,13 @@ class UtilCog(Scale): title="", description=user.mention, fields=fields, - color=Color.from_hex(str(user_roles[0].color) if user_roles else "#3498db"), + color=str(user_roles[0].color) if user_roles else "#3498db", ) embed.set_author( - name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar + name=f"{user.display_name}#{user.discriminator}", icon_url=user.display_avatar.url ) - embed.set_thumbnail(url=user.display_avatar) + embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"ID: {user.id}") await ctx.send(embed=embed) @@ -217,34 +218,35 @@ class UtilCog(Scale): async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild - owner = ( - f"{guild.owner.display_name}#{guild.owner.discriminator}" - if guild.owner - else "||`[redacted]`||" - ) + owner = await guild.get_owner() - 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 = [ - EmbedField(name="Owner", value=owner), - EmbedField(name="Channel Categories", value=categories), - EmbedField(name="Text Channels", value=text_channels), - EmbedField(name="Voice Channels", value=voice_channels), - EmbedField(name="Members", value=members), - EmbedField(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(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) @@ -258,13 +260,13 @@ class UtilCog(Scale): @slash_option( name="length", description="Password length (default 32)", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, ) @slash_option( name="chars", description="Characters to include (default last option)", - option_type=OptionTypes.INTEGER, + opt_type=OptionTypes.INTEGER, required=False, choices=[ SlashCommandChoice(name="A-Za-z", value=0), @@ -276,7 +278,7 @@ class UtilCog(Scale): @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, @@ -290,12 +292,12 @@ class UtilCog(Scale): f"Generated password:\n`{pw}`\n\n" '**WARNING: Once you press "Dismiss Message", ' "*the password is lost forever***", - hidden=True, + ephemeral=True, ) @slash_command(name="pigpen", description="Encode a string into pigpen") @slash_option( - name="text", description="Text to encode", option_type=OptionTypes.STRING, required=True + name="text", description="Text to encode", opt_type=OptionTypes.STRING, required=True ) async def _pigpen(self, ctx: InteractionContext, text: str) -> None: outp = "`" @@ -314,4 +316,4 @@ class UtilCog(Scale): 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 9c2af3e..28e3b89 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -89,4 +89,4 @@ class VerifyCog(Scale): def setup(bot: Snake) -> None: """Add VerifyCog to J.A.R.V.I.S.""" - bot.add_cog(VerifyCog(bot)) + VerifyCog(bot) From 78f916db5d67452a373593a7a47ac557e7b858ec Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:53:32 -0700 Subject: [PATCH 056/365] Add extra packages --- poetry.lock | 94 +++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 ++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index cc52890..f59cc4b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -278,6 +278,19 @@ 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" @@ -294,6 +307,14 @@ numpy = [ {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" @@ -513,6 +534,21 @@ urllib3 = ">=1.21.1,<1.27" 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" @@ -567,6 +603,24 @@ 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" @@ -627,7 +681,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "86fcc444c9ed72b727e6f3fed10dbd26bcd0654a7cf41b0f7286e3f8e207665b" +content-hash = "8a1e6e29ff70363abddad36082a494c4ce1f9cc672fe7aff30b6d5b596d50dac" [metadata.files] aiohttp = [ @@ -990,6 +1044,10 @@ numpy = [ {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"}, @@ -999,6 +1057,32 @@ opencv-python = [ {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"}, @@ -1259,6 +1343,10 @@ 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"}, @@ -1283,6 +1371,10 @@ 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"}, diff --git a/pyproject.toml b/pyproject.toml index 45956c2..7eee61c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,8 @@ 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"} From c6304b10bf776ed98eea13752461d84cc92ea4c5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:53:43 -0700 Subject: [PATCH 057/365] Add sync flag to config --- jarvis/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/config.py b/jarvis/config.py index 041b894..1146155 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -49,7 +49,7 @@ class Config(object): self.max_messages = max_messages self.gitlab_token = gitlab_token self.twitter = twitter - self.sync = sync or os.environ("SYNC_COMMANDS", False) + self.sync = sync or os.environ.get("SYNC_COMMANDS", False) self.__db_loaded = False self.__mongo = MongoClient(**self.mongo["connect"]) From 6fad4f37873f369b03899d0d5117dad0b9ae2d5e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:54:51 -0700 Subject: [PATCH 058/365] Update utils --- jarvis/utils/__init__.py | 27 +++++---------------------- jarvis/utils/permissions.py | 6 +++--- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 8306e5a..5c9a3c9 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -5,9 +5,7 @@ from pkgutil import iter_modules from typing import Any, Callable, Iterable, Optional, TypeVar import git -from dis_snek.models.discord.embed import Color, Embed -from dis_snek.models.discord.message import Message -from discord.ext import commands +from dis_snek.models.discord.embed import Embed import jarvis.cogs import jarvis.db @@ -28,15 +26,16 @@ def build_embed( ) -> Embed: """Embed builder utility function.""" if not timestamp: - timestamp = datetime.utcnow() + timestamp = datetime.now() embed = Embed( title=title, description=description, - color=parse_color_hex(color), + color=color, timestamp=timestamp, - fields=fields, **kwargs, ) + for field in fields: + embed.add_field(**field.to_dict()) return embed @@ -61,15 +60,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() @@ -77,13 +67,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)) - return Color.from_rgb(*rgb) - - def update() -> int: """J.A.R.V.I.S. update utility.""" repo = git.Repo(".") diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index 5a401eb..a20b686 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -1,5 +1,5 @@ """Permissions wrappers.""" -from dis_snek import Context, Permissions +from dis_snek import InteractionContext, Permissions from jarvis.config import get_config @@ -7,7 +7,7 @@ from jarvis.config import get_config def user_is_bot_admin() -> bool: """Check if a user is a J.A.R.V.I.S. admin.""" - def predicate(ctx: 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 @@ -20,7 +20,7 @@ def user_is_bot_admin() -> bool: def admin_or_permissions(*perms: list) -> bool: """Check if a user is an admin or has other perms.""" - async def predicate(ctx: Context) -> bool: + async def predicate(ctx: InteractionContext) -> bool: """Extended check predicate.""" # noqa: D401 is_admin = ctx.author.has_permission(Permissions.ADMINISTRATOR) has_other = any(ctx.author.has_permission(perm) for perm in perms) From 5629dd534f6dcab6dad1caadd154d09e8433ab5f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:55:05 -0700 Subject: [PATCH 059/365] Update init --- jarvis/__init__.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 47d572b..4ab5999 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,13 +1,13 @@ """Main J.A.R.V.I.S. package.""" import logging -from dis_snek import Intents, Snake +from dis_snek import Intents, Snake, listen from mongoengine import connect # 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() @@ -17,17 +17,17 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) logger.addHandler(file_handler) -intents = Intents.default() +intents = Intents.DEFAULT intents.members = True restart_ctx = None -jarvis = Snake(intents=intents, default_prefix=utils.get_prefix, sync_interactions=jconfig.sync) +jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) __version__ = "2.0.0a0" -@jarvis.add_listener +@listen() async def on_ready() -> None: """Lepton on_ready override.""" global restart_ctx @@ -35,6 +35,12 @@ async def on_ready() -> None: print(" Connected to {} guild(s)".format(len(jarvis.guilds))) +@listen() +async def on_startup() -> None: + """Lepton on_startup override.""" + tasks.init() + + def run() -> None: """Run J.A.R.V.I.S.""" connect( @@ -60,19 +66,11 @@ def run() -> None: ) 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) From d296d0496af1311580633a05be89ee22fac47711 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:57:41 -0700 Subject: [PATCH 060/365] Remove is_on_mobile check --- jarvis/cogs/util.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 0d18689..85023e8 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -92,10 +92,7 @@ class UtilCog(Scale): 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.", ephemeral=True) else: From 2d7b78f956d104404420a348f8d7ec72428e23f0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 09:59:52 -0700 Subject: [PATCH 061/365] guild.icon_url -> guild.icon.url --- jarvis/cogs/admin/ban.py | 6 +++--- jarvis/cogs/admin/kick.py | 2 +- jarvis/cogs/admin/warning.py | 6 +++--- jarvis/cogs/rolegiver.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 263239b..906f444 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -157,7 +157,7 @@ class BanCog(CacheCog): 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) @@ -366,12 +366,12 @@ 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]) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 28aaf79..b772aff 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -44,7 +44,7 @@ class KickCog(Scale): name=ctx.author.username + "#" + ctx.author.discriminator, icon_url=ctx.author.display_avatar, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) send_failed = False try: diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index aa75f70..c07c15c 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -115,7 +115,7 @@ class WarningCog(CacheCog): fields=[], ) embed.set_author(name=user.username, icon_url=user.display_avatar) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) else: fields = [] @@ -143,7 +143,7 @@ class WarningCog(CacheCog): name=user.username + "#" + user.discriminator, icon_url=user.display_avatar, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") pages.append(embed) else: @@ -170,7 +170,7 @@ class WarningCog(CacheCog): name=user.username + "#" + user.discriminator, icon_url=user.display_avatar, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) paginator = Paginator(bot=self.bot, *pages, timeout=300) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index b8e6b64..cac4764 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -68,7 +68,7 @@ class RolegiverCog(Scale): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( name=ctx.author.display_name, icon_url=ctx.author.display_avatar, @@ -140,7 +140,7 @@ class RolegiverCog(Scale): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( name=ctx.author.display_name, icon_url=ctx.author.display_avatar, @@ -186,7 +186,7 @@ class RolegiverCog(Scale): 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.username, icon_url=ctx.author.display_avatar, @@ -252,7 +252,7 @@ class RolegiverCog(Scale): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( name=ctx.author.display_name, icon_url=ctx.author.display_avatar, @@ -333,7 +333,7 @@ class RolegiverCog(Scale): fields=fields, ) - embed.set_thumbnail(url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( name=ctx.author.display_name, icon_url=ctx.author.display_avatar, From 82b906cdb15fd3bb4943dd4da115b45d04f02807 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 10:18:43 -0700 Subject: [PATCH 062/365] Fix avatars and component callback checks --- jarvis/cogs/admin/kick.py | 11 ++++------- jarvis/cogs/admin/mute.py | 21 ++++++--------------- jarvis/cogs/admin/roleping.py | 2 +- jarvis/cogs/admin/warning.py | 12 ++++-------- jarvis/cogs/modlog/command.py | 5 +---- jarvis/cogs/modlog/member.py | 5 +---- jarvis/cogs/modlog/message.py | 4 ++-- jarvis/cogs/modlog/utils.py | 5 +---- jarvis/cogs/remindme.py | 14 +++++++------- jarvis/cogs/starboard.py | 2 +- jarvis/events/message.py | 6 +++--- jarvis/tasks/reminder.py | 5 ++--- 12 files changed, 33 insertions(+), 59 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index b772aff..623ddbb 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -42,7 +42,7 @@ class KickCog(Scale): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.display_avatar, + icon_url=ctx.author.display_avatar.url, ) embed.set_thumbnail(url=ctx.guild.icon.url) @@ -60,12 +60,9 @@ class KickCog(Scale): fields=fields, ) - embed.set_author( - name=user.nick if user.nick else user.name, - icon_url=user.display_avatar, - ) - embed.set_thumbnail(url=user.display_avatar) - 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/mute.py b/jarvis/cogs/admin/mute.py index 8c36fbb..f34507d 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -88,11 +88,8 @@ class MuteCog(Scale): description=f"{user.mention} has been muted", fields=[EmbedField(name="Reason", value=reason)], ) - embed.set_author( - name=user.display_name, - icon_url=user.display_avatar, - ) - embed.set_thumbnail(url=user.display_avatar) + 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) @@ -118,17 +115,11 @@ class MuteCog(Scale): description=f"{user.mention} has been unmuted", fields=[], ) - embed.set_author( - name=user.display_name, - icon_url=user.display_avatar, - ) - embed.set_thumbnail(url=user.display_avatar) + 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, - ) - embed.set_thumbnail(url=user.display_avatar) + 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/roleping.py b/jarvis/cogs/admin/roleping.py index 1dc1561..6a01ede 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -111,7 +111,7 @@ class RolepingCog(CacheCog): if not admin: admin = self.bot.user - embed.set_author(name=admin.display_name, icon_url=admin.display_avatar) + 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) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index c07c15c..edd0db5 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -67,10 +67,7 @@ class WarningCog(CacheCog): description=f"{user.mention} has been warned", fields=fields, ) - embed.set_author( - name=user.display_name, - icon_url=user.display_avatar, - ) + 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) @@ -114,7 +111,7 @@ class WarningCog(CacheCog): description=f"{warnings.count()} total | 0 currently active", fields=[], ) - embed.set_author(name=user.username, icon_url=user.display_avatar) + embed.set_author(name=user.username, icon_url=user.display_avatar.url) embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) else: @@ -141,7 +138,7 @@ class WarningCog(CacheCog): ) embed.set_author( name=user.username + "#" + user.discriminator, - icon_url=user.display_avatar, + icon_url=user.display_avatar.url, ) embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") @@ -167,8 +164,7 @@ class WarningCog(CacheCog): fields=fields[i : i + 5], ) embed.set_author( - name=user.username + "#" + user.discriminator, - icon_url=user.display_avatar, + name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url ) embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index 884fac5..75d54b5 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -41,10 +41,7 @@ class ModlogCommandCog(commands.Cog): fields=fields, color="#fc9e3f", ) - embed.set_author( - name=ctx.author.username, - icon_url=ctx.author.display_avatar, - ) + 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}" ) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py index 9453e19..99302d7 100644 --- a/jarvis/cogs/modlog/member.py +++ b/jarvis/cogs/modlog/member.py @@ -322,10 +322,7 @@ class ModlogMemberCog(commands.Cog): fields=fields, timestamp=log.created_at, ) - embed.set_author( - name=f"{after.name}", - icon_url=after.display_avatar, - ) + 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 853680b..a00bb69 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -44,7 +44,7 @@ class ModlogMessageCog(commands.Cog): ) embed.set_author( name=before.author.name, - icon_url=before.author.display_avatar, + icon_url=before.author.display_avatar.url, url=after.jump_url, ) embed.set_footer( @@ -99,7 +99,7 @@ class ModlogMessageCog(commands.Cog): embed.set_author( name=message.author.name, - icon_url=message.author.display_avatar, + icon_url=message.author.display_avatar.url, url=message.jump_url, ) embed.set_footer( diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py index 4344ff8..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.display_avatar, - ) + 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/remindme.py b/jarvis/cogs/remindme.py index e1973ca..73a3214 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -146,9 +146,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.display_avatar, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.display_avatar) + embed.set_thumbnail(url=ctx.author.display_avatar.url) await ctx.send(embed=embed) @@ -174,9 +174,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.display_avatar, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.display_avatar) + embed.set_thumbnail(url=ctx.author.display_avatar.url) return embed @@ -233,7 +233,7 @@ class RemindmeCog(CacheCog): try: context = await self.bot.wait_for_component( - check=lambda x: ctx.author.id == x.author_id, + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) @@ -261,9 +261,9 @@ class RemindmeCog(CacheCog): embed.set_author( name=ctx.author.username + "#" + ctx.author.discriminator, - icon_url=ctx.author.display_avatar, + icon_url=ctx.author.display_avatar.url, ) - embed.set_thumbnail(url=ctx.author.display_avatar) + embed.set_thumbnail(url=ctx.author.display_avatar.url) await context.context.edit_origin( content=f"Deleted {len(context.context.values)} reminder(s)", diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 2f39d5f..af9d425 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -201,7 +201,7 @@ class StarboardCog(Scale): embed.set_author( name=message.author.display_name, url=message.jump_url, - icon_url=message.author.display_avatar, + icon_url=message.author.display_avatar.url, ) embed.set_footer(text=message.guild.name + " | " + message.channel.name) if image_url: diff --git a/jarvis/events/message.py b/jarvis/events/message.py index ceb73ef..f62dc4a 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -85,7 +85,7 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar, + icon_url=message.author.display_avatar.url, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 @@ -121,7 +121,7 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar, + icon_url=message.author.display_avatar.url, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" @@ -191,7 +191,7 @@ class MessageEventHandler(object): ) embed.set_author( name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar, + icon_url=message.author.display_avatar.url, ) embed.set_footer( text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index a4b1601..4c65e8e 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -25,10 +25,9 @@ async def _remind() -> None: fields=[], ) embed.set_author( - name=user.name + "#" + user.discriminator, - icon_url=user.display_avatar, + name=user.name + "#" + user.discriminator, icon_url=user.display_avatar.url ) - embed.set_thumbnail(url=user.display_avatar) + embed.set_thumbnail(url=user.display_avatar.url) try: await user.send(embed=embed) except Exception: From b142e7bcdb2ca441ca95a16d46baf556d4f2d507 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 10:23:07 -0700 Subject: [PATCH 063/365] Fix interaction disable --- jarvis/cogs/remindme.py | 8 ++++---- jarvis/cogs/twitter.py | 16 ++++++++-------- jarvis/cogs/verify.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 73a3214..70bd916 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -241,8 +241,8 @@ class RemindmeCog(CacheCog): _ = 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.context.values, reminders): @@ -272,8 +272,8 @@ class RemindmeCog(CacheCog): ) 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) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 33ed8e6..6bc8b81 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -132,8 +132,8 @@ class TwitterCog(Scale): 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 + for component in row.components: + component.disabled = True block = "\n".join(handlemap[x] for x in context.context.values) await context.context.edit_origin( @@ -141,8 +141,8 @@ class TwitterCog(Scale): ) 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) @slash_command( @@ -197,8 +197,8 @@ class TwitterCog(Scale): t.save() for row in components: - for component in row["components"]: - component["disabled"] = True + for component in row.components: + component.disabled = True block = "\n".join(handlemap[x] for x in context.context.values) await context.context.edit_origin( @@ -211,8 +211,8 @@ class TwitterCog(Scale): ) 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) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 28e3b89..1202ea4 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -61,8 +61,8 @@ class VerifyCog(Scale): correct = context.context.custom_id.split("||")[-1] == "yes" if correct: for row in components: - for component in row["components"]: - component["disabled"] = True + 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") From 2595b87061feb2e10f341d7c6450e81ea2cd7ccd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 10:33:17 -0700 Subject: [PATCH 064/365] Fix rolegiver --- jarvis/cogs/rolegiver.py | 69 +++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index cac4764..8b87241 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -69,10 +69,7 @@ class RolegiverCog(Scale): ) embed.set_thumbnail(url=ctx.guild.icon.url) - embed.set_author( - name=ctx.author.display_name, - icon_url=ctx.author.display_avatar, - ) + 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}") @@ -106,17 +103,21 @@ class RolegiverCog(Scale): message = await ctx.send(content="\u200b", components=components) try: context = await self.bot.wait_for_component( - self.bot, - check=lambda x: ctx.author.id == x.author.id, + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 1, ) + 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: @@ -129,8 +130,9 @@ class RolegiverCog(Scale): 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 = [ - EmbedField(name="Removed Role", value=f"{role.mention}"), + EmbedField(name="Removed Role(s)", value=rvalue), EmbedField(name="Remaining Role(s)", value=value), ] @@ -141,24 +143,21 @@ class RolegiverCog(Scale): ) embed.set_thumbnail(url=ctx.guild.icon.url) - embed.set_author( - name=ctx.author.display_name, - icon_url=ctx.author.display_avatar, - ) + 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}" ) await context.context.edit_origin( - content=f"Removed {len(context.selected_options)} role(s)", + 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) @slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles") @@ -188,8 +187,8 @@ class RolegiverCog(Scale): embed.set_thumbnail(url=ctx.guild.icon.url) embed.set_author( - name=ctx.author.nick if ctx.author.nick else ctx.author.username, - icon_url=ctx.author.display_avatar, + 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}") @@ -212,8 +211,7 @@ class RolegiverCog(Scale): select = Select( options=options, - custom_id="to_delete", - placeholder="Select roles to remove", + placeholder="Select roles to add", min_values=1, max_values=len(options), ) @@ -223,7 +221,7 @@ class RolegiverCog(Scale): try: context = await self.bot.wait_for_component( - check=lambda x: ctx.author.id == x.author_id, + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) @@ -248,14 +246,14 @@ class RolegiverCog(Scale): 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_author( name=ctx.author.display_name, - icon_url=ctx.author.display_avatar, + icon_url=ctx.author.display_avatar.url, ) embed.set_footer( @@ -263,14 +261,14 @@ class RolegiverCog(Scale): ) for row in components: - for component in row["components"]: - component["disabled"] = True + 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) @slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role") @@ -305,7 +303,7 @@ class RolegiverCog(Scale): try: context = await self.bot.wait_for_component( - check=lambda x: ctx.author.id == x.author_id, + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) @@ -329,30 +327,27 @@ class RolegiverCog(Scale): embed = build_embed( title="User Forfeited Role", - description=f"{role.mention} taken from {ctx.author.mention}", + 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, - ) + 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 + 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 + for component in row.components: + component.disabled = True await message.edit(components=components) @slash_command( From 1cf4108a44da29f0a1a23f8e497a2f8a67c4aeb8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 11:41:47 -0700 Subject: [PATCH 065/365] Fix starboard --- jarvis/cogs/starboard.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index af9d425..79442e6 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -95,13 +95,14 @@ class StarboardCog(Scale): 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}.", ephemeral=True) + await ctx.send(f"Starboard deleted from {channel.mention}.") else: await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True) @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) async def _star_message(self, ctx: InteractionContext) -> None: - await self._star_add.invoke(ctx, ctx.target_message) + await self._star_add._can_run(ctx) + await self._star_add.callback(ctx, message=str(ctx.target_id)) @slash_command(name="star", sub_cmd_name="add", description="Star a message") @slash_option( @@ -159,10 +160,10 @@ class StarboardCog(Scale): 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])] + starboard = channel_list[int(com_ctx.context.values[0])] exists = Star.objects( message=message.id, @@ -220,16 +221,16 @@ class StarboardCog(Scale): 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, ) @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") @slash_option( - name="message", description="Star to delete", opt_type=OptionTypes.INTEGER, required=True + name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True ) @slash_option( name="starboard", @@ -265,14 +266,14 @@ class StarboardCog(Scale): 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: Snake) -> None: From 56bd5edc93b453d64d4a2c74caf50df93f1e6f46 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 12:03:07 -0700 Subject: [PATCH 066/365] Fix twitter, update twitter info to use ids instead of handles --- jarvis/cogs/twitter.py | 12 +++++++----- jarvis/db/models.py | 2 ++ jarvis/tasks/twitter.py | 23 +++++++++++++---------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 6bc8b81..bd92ddc 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -67,7 +67,8 @@ class TwitterCog(Scale): return try: - latest_tweet = await asyncio.to_thread(self.api.user_timeline, screen_name=handle)[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?", ephemeral=True @@ -79,13 +80,14 @@ class TwitterCog(Scale): 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", ephemeral=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, @@ -125,7 +127,7 @@ class TwitterCog(Scale): try: context = await self.bot.wait_for_component( - check=lambda x: ctx.author.id == x.author.id, + check=lambda x: ctx.author.id == x.context.author.id, messages=message, timeout=60 * 5, ) 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/tasks/twitter.py b/jarvis/tasks/twitter.py index fe1a9c1..3167e32 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -1,6 +1,7 @@ """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 @@ -23,18 +24,20 @@ async def _tweets() -> None: guild_cache = dict() channel_cache = dict() twitters = Twitter.objects(active=True) - handles = Twitter.objects.distinct("handle") - twitter_data = dict() - for handle in handles: - try: - data = __api.user_timeline(screen_name=handle) - twitter_data[handle] = data - except Exception as e: - logger.error(f"Error with fetching: {e}") for twitter in twitters: try: - tweets = list(filter(lambda x: x.id > twitter.last_tweet, twitter_data[twitter.handle])) - if tweets: + 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) From 56661d96b857409842968ba232cd44dd834dd701 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 12:15:21 -0700 Subject: [PATCH 067/365] Update pre-commit --- .flake8 | 13 +++++++++++++ .pre-commit-config.yaml | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..7b3133a --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +[flake8] +extend-ignore = + Q0, E501, C812, E203, W503 # These default to arguing with Black. We might configure some of them eventually + 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 7034078..b88c6fe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,9 +4,12 @@ repos: 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] @@ -19,7 +22,8 @@ repos: rev: 22.1.0 hooks: - id: black - args: [--line-length=100, --fast, --target-version=py310] + args: [--line-length=100, --target-version=py310] + language_version: python3.10 - repo: https://github.com/pre-commit/mirrors-isort rev: V5.10.1 @@ -32,7 +36,14 @@ repos: hooks: - id: flake8 additional_dependencies: - - flake8-annotations~=2.7 + - flake8-annotations~=2.0 - flake8-bandit~=2.1 - - flake8-docstrings~=1.6 - args: [--max-line-length=100, --ignore=ANN101 D107 ANN102 ANN206 D105 ANN204 E203] + - flake8-docstrings~=1.5 + - flake8-bugbear + - flake8-comprehensions + - flake8-quotes + - flake8-raise + - flake8-deprecated + - flake8-print + - flake8-return + language_version: python3.10 From 9647f9c7468922d317b92c2d80a0d303c54faa3e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 13:04:51 -0700 Subject: [PATCH 068/365] Remove implentations for time being --- .flake8 | 3 +- jarvis/cogs/admin/lock.py | 235 ++++++++++++++++------------------ jarvis/cogs/admin/lockdown.py | 17 +-- 3 files changed, 118 insertions(+), 137 deletions(-) diff --git a/.flake8 b/.flake8 index 7b3133a..2ccbf95 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] extend-ignore = - Q0, E501, C812, E203, W503 # These default to arguing with Black. We might configure some of them eventually + Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually + ANN101, # Ignore self annotation 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. diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index eaf4f13..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", - choices=[ - 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", - choices=[ - 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 6f7055c..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): @@ -25,30 +26,30 @@ class LockdownCog(CacheCog): 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: From e9500d6ce18308dcea20aab76a4dca0641f56003 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 13:06:28 -0700 Subject: [PATCH 069/365] Ignore implementation for time being --- jarvis/cogs/settings.py | 47 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 4bc8973..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 @@ -42,15 +43,15 @@ class SettingsCog(commands.Cog): 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}") @@ -64,15 +65,15 @@ class SettingsCog(commands.Cog): 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}") @@ -86,12 +87,12 @@ class SettingsCog(commands.Cog): 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) @@ -106,12 +107,12 @@ class SettingsCog(commands.Cog): 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) @@ -126,12 +127,12 @@ class SettingsCog(commands.Cog): 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) @@ -146,12 +147,12 @@ class SettingsCog(commands.Cog): 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) @@ -163,7 +164,7 @@ class SettingsCog(commands.Cog): 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.") @@ -174,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.") @@ -185,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) @@ -197,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) @@ -209,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) @@ -237,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 @@ -250,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}`") @@ -258,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) From ace64e9ad9f7381768d4a959293c176657d32443 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 13:15:03 -0700 Subject: [PATCH 070/365] New pre-commit compliance --- .flake8 | 2 +- jarvis/__init__.py | 6 +-- jarvis/cogs/admin/ban.py | 4 +- jarvis/cogs/dbrand.py | 1 - jarvis/cogs/verify.py | 3 +- jarvis/config.py | 3 +- jarvis/tasks/twitter.py | 4 +- jarvis/utils/__init__.py | 113 ++++++++++++++++++++++++++++++++------- 8 files changed, 104 insertions(+), 32 deletions(-) diff --git a/.flake8 b/.flake8 index 2ccbf95..6e7cd96 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] extend-ignore = Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually - ANN101, # Ignore self annotation + 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. diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 4ab5999..9766919 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -31,8 +31,8 @@ __version__ = "2.0.0a0" async def on_ready() -> None: """Lepton on_ready override.""" global restart_ctx - print(" Logged in as {0.user}".format(jarvis)) - print(" Connected to {} guild(s)".format(len(jarvis.guilds))) + print(" Logged in as {0.user}".format(jarvis)) # noqa: T001 + print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 @listen() @@ -60,7 +60,7 @@ def run() -> None: 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) ) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 906f444..d72dd08 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -15,7 +15,7 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import check from jarvis.db.models import Ban, Unban -from jarvis.utils import build_embed, find +from jarvis.utils import build_embed, find, find_all from jarvis.utils.cachecog import CacheCog from jarvis.utils.permissions import admin_or_permissions @@ -219,7 +219,7 @@ class BanCog(CacheCog): bans, ) else: - results = [x for x in filter(lambda x: x.user.username == user, bans)] + results = find_all(lambda x: x.user.username == user, bans) if results: if len(results) > 1: active_bans = [] diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index a6f0f41..9a14251 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -174,7 +174,6 @@ class DbrandCog(Scale): elif search == "🏳️": search = "fr" else: - print(search) await ctx.send("Please use text to search for shipping.") return if len(search) > 2: diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 1202ea4..2d1db38 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -26,8 +26,7 @@ def create_layout() -> list: custom_id=f"verify_button||{id}", ) ) - action_row = spread_to_rows(*buttons, max_in_row=3) - return action_row + return spread_to_rows(*buttons, max_in_row=3) class VerifyCog(Scale): diff --git a/jarvis/config.py b/jarvis/config.py index 1146155..c2b9983 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -65,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/tasks/twitter.py b/jarvis/tasks/twitter.py index 3167e32..46638f5 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -21,8 +21,8 @@ __api = tweepy.API(__auth) async def _tweets() -> None: """J.A.R.V.I.S. twitter blocking task.""" - guild_cache = dict() - channel_cache = dict() + guild_cache = {} + channel_cache = {} twitters = Twitter.objects(active=True) for twitter in twitters: try: diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 5c9a3c9..11318e1 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,8 +1,7 @@ """J.A.R.V.I.S. Utility Functions.""" from datetime import datetime -from operator import attrgetter from pkgutil import iter_modules -from typing import Any, Callable, Iterable, Optional, TypeVar +from typing import Any, Callable, Iterable, List, Optional, TypeVar import git from dis_snek.models.discord.embed import Embed @@ -88,25 +87,101 @@ def get_repo_hash() -> str: return repo.head.object.hexsha -def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: - for element in seq: - if predicate(element): - return element +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 get(iterable: Iterable[T], **attrs: Any) -> Optional[T]: - if len(attrs) == 1: - k, v = attrs.popitem() - pred = attrgetter(k.replace("__", ".")) - for elem in iterable: - if pred(elem) == v: - return elem - return None +def find_all(predicate: Callable, sequence: Iterable) -> List[Any]: + """ + Find all elements in a sequence that match the predicate. - converted = [(attrgetter(attr.replace("__", ".")), value) for attr, value in attrs.items()] + ??? 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 - for elem in iterable: - if all(pred(elem) == value for pred, value in converted): - return elem - return None + 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 From 43ff9fce5d3a10e75913d564ba1e8f4881faa305 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 4 Feb 2022 13:18:39 -0700 Subject: [PATCH 071/365] Check platform to change format string --- jarvis/cogs/util.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 85023e8..936c855 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Utility Cog.""" +import platform import re import secrets import string @@ -179,15 +180,18 @@ class UtilCog(Scale): user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) + format_string = "%a, %b %-d, %Y %-I:%M %p" + if platform.system() == "Windows": + format_string = "%a, %b %#d, %Y %#I:%M %p" fields = [ EmbedField( name="Joined", - value=user.joined_at.strftime("%a, %b %#d, %Y %#I:%M %p"), + value=user.joined_at.strftime(format_string), ), EmbedField( name="Registered", - value=user.created_at.strftime("%a, %b %#d, %Y %#I:%M %p"), + value=user.created_at.strftime(format_string), ), EmbedField( name=f"Roles [{len(user_roles)}]", From db5a60a88f58f47328001a155dd20c481cc93399 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 13:22:48 -0700 Subject: [PATCH 072/365] Optimize reminders, changed how reminder time is chosen, closes #122 --- jarvis/cogs/remindme.py | 128 ++++++++++++++++++++------------------- jarvis/db/models.py | 1 + jarvis/tasks/reminder.py | 9 ++- jarvis/utils/__init__.py | 6 +- 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 70bd916..9639485 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,14 +2,16 @@ import asyncio import re from datetime import datetime, timedelta -from typing import List, Optional +from typing import List from bson import ObjectId from dis_snek import InteractionContext, Snake +from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommandChoice, slash_command, slash_option, ) @@ -19,6 +21,7 @@ from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") +time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?") invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, @@ -39,35 +42,29 @@ class RemindmeCog(CacheCog): required=True, ) @slash_option( - name="weeks", - description="Number of weeks?", - opt_type=OptionTypes.INTEGER, + name="delay", + description="How long? (i.e. 1w 3d 7h 5m 20s)", + opt_type=OptionTypes.STRING, required=False, ) @slash_option( - name="days", description="Number of days?", opt_type=OptionTypes.INTEGER, required=False - ) - @slash_option( - name="hours", - description="Number of hours?", - opt_type=OptionTypes.INTEGER, - required=False, - ) - @slash_option( - name="minutes", - description="Number of minutes?", - opt_type=OptionTypes.INTEGER, + name="private", + description="Send as DM?", + opt_type=OptionTypes.STRING, required=False, + choices=[ + SlashCommandChoice(name="Yes", value="y"), + SlashCommandChoice(name="No", value="n"), + ], ) async def _remindme( self, ctx: InteractionContext, - message: Optional[str] = None, - weeks: Optional[int] = 0, - days: Optional[int] = 0, - hours: Optional[int] = 0, - minutes: Optional[int] = 0, + message: str, + delay: str, + private: str = "n", ) -> None: + private = private == "y" if len(message) > 100: await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True) return @@ -81,31 +78,22 @@ class RemindmeCog(CacheCog): await ctx.send("Hey, you should probably make this readable", ephemeral=True) return - if not any([weeks, days, hours, minutes]): + units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"} + delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0} + + if times := time_pattern.findall(delay, flags=re.I): + for t in times: + delta[units[t[-1]]] += float(t[:-1]) + else: + await ctx.send( + "Invalid time string, please follow example: `1w 3d 7h 5m 20s`", ephemeral=True + ) + return + + if not any(value for value in delta.items()): await ctx.send("At least one time period is required", ephemeral=True) return - weeks = abs(weeks) - days = abs(days) - hours = abs(hours) - minutes = abs(minutes) - - if weeks and weeks > 4: - await ctx.send("Cannot be farther than 4 weeks out!", ephemeral=True) - return - - elif days and days > 6: - await ctx.send("Use weeks instead of 7+ days, please.", ephemeral=True) - return - - elif hours and hours > 23: - await ctx.send("Use days instead of 24+ hours, please.", ephemeral=True) - return - - elif minutes and minutes > 59: - await ctx.send("Use hours instead of 59+ minutes, please.", ephemeral=True) - return - reminders = Reminder.objects(user=ctx.author.id, active=True).count() if reminders >= 5: await ctx.send( @@ -115,12 +103,7 @@ class RemindmeCog(CacheCog): ) return - remind_at = datetime.utcnow() + timedelta( - weeks=weeks, - days=days, - hours=hours, - minutes=minutes, - ) + remind_at = datetime.now() + timedelta(**delta) _ = Reminder( user=ctx.author_id, @@ -128,6 +111,7 @@ class RemindmeCog(CacheCog): guild=ctx.guild.id, message=message, remind_at=remind_at, + private=private, active=True, ).save() @@ -150,7 +134,7 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.display_avatar.url) - await ctx.send(embed=embed) + await ctx.send(embed=embed, ephemeral=private) async def get_reminders_embed( self, ctx: InteractionContext, reminders: List[Reminder] @@ -158,13 +142,22 @@ class RemindmeCog(CacheCog): """Build embed for paginator.""" fields = [] for reminder in reminders: - fields.append( - EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), - value=f"{reminder.message}\n\u200b", - inline=False, + if reminder.private and isinstance(ctx.channel, GuildChannel): + fields.embed( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value="Please DM me this command to view the content of this reminder", + inline=False, + ) + ) + else: + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=f"{reminder.message}\n\u200b", + inline=False, + ) ) - ) embed = build_embed( title=f"{len(reminders)} Active Reminder(s)", @@ -246,13 +239,22 @@ class RemindmeCog(CacheCog): fields = [] for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): - fields.append( - EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), - value=reminder.message, - inline=False, + if reminder.private and isinstance(ctx.channel, GuildChannel): + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value="Private reminder", + inline=False, + ) + ) + else: + fields.append( + EmbedField( + name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=reminder.message, + inline=False, + ) ) - ) embed = build_embed( title="Deleted Reminder(s)", description="", @@ -260,7 +262,7 @@ class RemindmeCog(CacheCog): ) embed.set_author( - name=ctx.author.username + "#" + ctx.author.discriminator, + name=ctx.author.display_name + "#" + ctx.author.discriminator, icon_url=ctx.author.display_avatar.url, ) embed.set_thumbnail(url=ctx.author.display_avatar.url) diff --git a/jarvis/db/models.py b/jarvis/db/models.py index a99cb45..5c9b145 100644 --- a/jarvis/db/models.py +++ b/jarvis/db/models.py @@ -155,6 +155,7 @@ class Reminder(Document): message = StringField(max_length=100, required=True) remind_at = DateTimeField(required=True) created_at = DateTimeField(default=datetime.utcnow) + private = BooleanField(default=False) meta = {"db_alias": "main"} diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index 4c65e8e..b897e8e 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -25,7 +25,7 @@ async def _remind() -> None: fields=[], ) embed.set_author( - name=user.name + "#" + user.discriminator, icon_url=user.display_avatar.url + name=user.username + "#" + user.discriminator, icon_url=user.avatar.url ) embed.set_thumbnail(url=user.display_avatar.url) try: @@ -33,8 +33,13 @@ async def _remind() -> None: except Exception: guild = jarvis.jarvis.fetch_guild(reminder.guild) channel = guild.get_channel(reminder.channel) if guild else None - if channel: + if channel and not reminder.private: await channel.send(f"{user.mention}", embed=embed) + else: + await channel.send( + f"{user.mention}, you had a private reminder set for now, " + "but I couldn't send it to you." + ) finally: reminder.delete() diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 11318e1..c5d8b19 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -125,11 +125,7 @@ def find_all(predicate: Callable, sequence: Iterable) -> List[Any]: A list of matches """ - matches = [] - for el in sequence: - if predicate(el): - matches.append(el) - return matches + return [el for el in sequence if predicate(el)] def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]: From 5917f252e1992769d092347a4d645f19469b2819 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 13:23:56 -0700 Subject: [PATCH 073/365] Formatting changes --- jarvis/cogs/admin/ban.py | 25 ++++++++++++------------- jarvis/cogs/admin/kick.py | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index d72dd08..4f3cdf4 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -256,10 +256,11 @@ class BanCog(CacheCog): if not discord_ban_info and not database_ban_info: await ctx.send(f"Unable to find user {orig_user}", ephemeral=True) - elif discord_ban_info: + elif discord_ban_info and not database_ban_info: await self.discord_apply_unban(ctx, discord_ban_info.user, reason) + else: - discord_ban_info = find(lambda x: x.user.id == database_ban_info["id"], bans) + discord_ban_info = find(lambda x: x.user.id == database_ban_info.id, bans) if discord_ban_info: await self.discord_apply_unban(ctx, discord_ban_info.user, reason) else: @@ -273,9 +274,7 @@ class BanCog(CacheCog): admin=ctx.author.id, reason=reason, ).save() - await ctx.send( - "Unable to find user in Discord, " + "but removed entry from database." - ) + await ctx.send("Unable to find user in Discord, but removed entry from database.") @slash_command( name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans" @@ -300,9 +299,9 @@ class BanCog(CacheCog): choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], ) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) - async def _bans_list(self, ctx: InteractionContext, type: int = 0, active: int = 1) -> None: + async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, type=type, active=active) + exists = self.check_cache(ctx, btype=btype, active=active) if exists: await ctx.defer(ephemeral=True) await ctx.send( @@ -314,8 +313,8 @@ class BanCog(CacheCog): search = {"guild": ctx.guild.id} if active: search["active"] = True - if type > 0: - search["type"] = types[type] + if btype > 0: + search["type"] = types[btype] bans = Ban.objects(**search).order_by("-created_at") db_bans = [] fields = [] @@ -355,9 +354,9 @@ class BanCog(CacheCog): pages = [] title = "Active " if active else "Inactive " - if type > 0: - title += types[type] - if type == 1: + if btype > 0: + title += types[btype] + if btype == 1: title += "a" title += "bans" if len(fields) == 0: @@ -381,7 +380,7 @@ class BanCog(CacheCog): "user": ctx.author.id, "timeout": datetime.utcnow() + timedelta(minutes=5), "command": ctx.subcommand_name, - "type": type, + "btype": btype, "active": active, "paginator": paginator, } diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 623ddbb..eb09b2a 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -33,6 +33,7 @@ class KickCog(Scale): if len(reason) > 100: await ctx.send("Reason must be < 100 characters", ephemeral=True) return + guild_name = ctx.guild.name embed = build_embed( title=f"You have been kicked from {guild_name}", @@ -64,10 +65,10 @@ class KickCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - await ctx.send(embed=embed) _ = Kick( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, ).save() + await ctx.send(embed=embed) From adf770d624705e0695d6369d60a6c575be9819a5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 5 Feb 2022 23:50:21 -0700 Subject: [PATCH 074/365] Optimization review, ref #120 --- jarvis/cogs/admin/mute.py | 4 -- jarvis/cogs/admin/purge.py | 11 +++- jarvis/cogs/admin/roleping.py | 33 ++--------- jarvis/cogs/admin/warning.py | 21 +------ jarvis/cogs/gitlab.py | 62 ++------------------ jarvis/cogs/image.py | 6 +- jarvis/events/member.py | 7 +-- jarvis/events/message.py | 13 +---- jarvis/logo.py | 106 ---------------------------------- 9 files changed, 29 insertions(+), 234 deletions(-) delete mode 100644 jarvis/logo.py diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index f34507d..9733e6e 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -119,7 +119,3 @@ class MuteCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_thumbnail(url=user.display_avatar.url) - embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index e86cb1a..78a2c46 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -31,11 +31,11 @@ class PurgeCog(Scale): await ctx.send("Amount must be >= 1", ephemeral=True) return await ctx.defer() - channel = ctx.channel + messages = [] - async for message in channel.history(limit=amount + 1): + async for message in ctx.channel.history(limit=amount + 1): messages.append(message) - await channel.delete_messages(messages) + await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") _ = Purge( channel=ctx.channel.id, guild=ctx.guild.id, @@ -71,16 +71,19 @@ class PurgeCog(Scale): elif delay > 300: await ctx.send("Delay must be < 5 minutes", ephemeral=True) return + autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first() if autopurge: await ctx.send("Autopurge already exists.", ephemeral=True) return + _ = Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, ).save() + await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") @slash_command( @@ -126,6 +129,8 @@ class PurgeCog(Scale): if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return + autopurge.delay = delay autopurge.save() + await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.") diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 6a01ede..4d5fb70 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,7 +1,5 @@ """J.A.R.V.I.S. RolepingCog.""" -from datetime import datetime, timedelta - -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -14,17 +12,13 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import check from jarvis.db.models import Roleping -from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog +from jarvis.utils import build_embed, find_all from jarvis.utils.permissions import admin_or_permissions -class RolepingCog(CacheCog): +class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command( name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" ) @@ -35,6 +29,7 @@ class RolepingCog(CacheCog): if roleping: await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return + _ = Roleping( role=role.id, guild=ctx.guild.id, @@ -60,14 +55,6 @@ class RolepingCog(CacheCog): @slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles") async def _roleping_list(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return rolepings = Roleping.objects(guild=ctx.guild.id) if not rolepings: @@ -77,8 +64,8 @@ class RolepingCog(CacheCog): embeds = [] for roleping in rolepings: role = await ctx.guild.get_role(roleping.role) - bypass_roles = list(filter(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles)) - bypass_roles = [r.mention or "||`[redacted]`||" for r in bypass_roles] + broles = find_all(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles) + bypass_roles = [r.mention or "||`[redacted]`||" for r in broles] bypass_users = [ await ctx.guild.get_member(u).mention or "||`[redacted]`||" for u in roleping.bypass["users"] @@ -118,14 +105,6 @@ class RolepingCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *embeds, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index edd0db5..7145cca 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,6 +1,4 @@ """J.A.R.V.I.S. WarningCog.""" -from datetime import datetime, timedelta - from dis_snek import InteractionContext, Permissions, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.user import User @@ -87,14 +85,7 @@ class WarningCog(CacheCog): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, user_id=user.id, active=active) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return + warnings = Warning.objects( user=user.id, guild=ctx.guild.id, @@ -171,14 +162,4 @@ class WarningCog(CacheCog): paginator = Paginator(bot=self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "guild": ctx.guild.id, - "user": ctx.author.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "user_id": user.id, - "active": active, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index 6c769b0..f23062b 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. GitLab Cog.""" -from datetime import datetime, timedelta +from datetime import datetime import gitlab -from dis_snek import InteractionContext, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.snek.application_commands import ( @@ -14,16 +14,15 @@ from dis_snek.models.snek.application_commands import ( from jarvis.config import get_config from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog guild_ids = [862402786116763668] -class GitlabCog(CacheCog): +class GitlabCog(Scale): """J.A.R.V.I.S. GitLab Cog.""" def __init__(self, bot: Snake): - super().__init__(bot) + self.bot = bot config = get_config() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) # J.A.R.V.I.S. GitLab ID is 29 @@ -236,10 +235,11 @@ class GitlabCog(CacheCog): title += f"J.A.R.V.I.S. {name}s" fields = [] for item in api_list: + description = item.description or "No description" fields.append( EmbedField( name=f"[#{item.iid}] {item.title}", - value=item.description + f"\n\n[View this {name}]({item.web_url})", + value=(description[:200] + f"...\n\n[View this {name}]({item.web_url})"), inline=False, ) ) @@ -275,14 +275,6 @@ class GitlabCog(CacheCog): ], ) async def _issues(self, ctx: InteractionContext, state: str = "opened") -> None: - exists = self.check_cache(ctx, state=state) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return await ctx.defer() m_state = state if m_state == "all": @@ -319,15 +311,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "state": state, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( @@ -348,14 +331,6 @@ class GitlabCog(CacheCog): ], ) async def _mergerequests(self, ctx: InteractionContext, state: str = "opened") -> None: - exists = self.check_cache(ctx, state=state) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - "Please use existing interaction: " + f"{exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return await ctx.defer() m_state = state if m_state == "all": @@ -394,15 +369,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "state": state, - "paginator": paginator, - } - await paginator.send(ctx) @slash_command( @@ -412,14 +378,6 @@ class GitlabCog(CacheCog): scopes=guild_ids, ) async def _milestones(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return await ctx.defer() milestones = [] page = 1 @@ -450,14 +408,6 @@ class GitlabCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "user": ctx.author.id, - "guild": ctx.guild.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 83aff59..27df52e 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -13,6 +13,8 @@ from dis_snek.models.snek.cooldowns import Buckets from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize +MIN_ACCURACY = 0.80 + class ImageCog(Scale): """ @@ -64,8 +66,8 @@ class ImageCog(Scale): ratio = max(tgt_size / size - 0.02, 0.50) accuracy = 0.0 - # TODO: Optimize to not run multiple times - while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < 0.65): + + while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < MIN_ACCURACY): old_file = file buffer = np.frombuffer(file, dtype=np.uint8) diff --git a/jarvis/events/member.py b/jarvis/events/member.py index ee1043f..44a42c4 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -2,7 +2,7 @@ from dis_snek import Snake, listen from dis_snek.models.discord.user import Member -from jarvis.db.models import Mute, Setting +from jarvis.db.models import Setting class MemberEventHandler(object): @@ -16,11 +16,6 @@ class MemberEventHandler(object): async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild - mute = Mute.objects(guild=guild.id, user=user.id, active=True).first() - if mute: - mute_role = Setting.objects(guild=guild.id, setting="mute").first() - role = guild.get_role(mute_role.value) - await user.add_roles(role, reason="User is still muted from prior mute") unverified = Setting.objects(guild=guild.id, setting="unverified").first() if unverified: role = guild.get_role(unverified.value) diff --git a/jarvis/events/message.py b/jarvis/events/message.py index f62dc4a..0433cc4 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -8,7 +8,7 @@ from dis_snek.models.discord.message import Message from jarvis.config import get_config from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.utils import build_embed, find +from jarvis.utils import build_embed, find, find_all invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 @@ -150,13 +150,13 @@ class MessageEventHandler(object): roleping_ids = [r.role for r in rolepings] # Get roles in rolepings - role_in_rolepings = list(filter(lambda x: x in roleping_ids, roles)) + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) # Check if the user has the role, so they are allowed to ping it user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) # Admins can ping whoever - user_is_admin = message.author.guild_permissions.administrator + user_is_admin = message.author.guild_permissions.ADMINISTRATOR # Check if user in a bypass list user_has_bypass = False @@ -217,10 +217,3 @@ class MessageEventHandler(object): await self.checks(after) await self.roleping(after) await self.checks(after) - """Handle on_message_edit event. Calls other event handlers.""" - if not isinstance(after.channel, DMChannel) and not after.author.bot: - await self.massmention(after) - await self.roleping(after) - await self.checks(after) - await self.roleping(after) - await self.checks(after) diff --git a/jarvis/logo.py b/jarvis/logo.py deleted file mode 100644 index 79e550c..0000000 --- a/jarvis/logo.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Logos for J.A.R.V.I.S.""" - -logo_doom = r""" - ___ ___ ______ _ _ _____ _____ - |_ | / _ \ | ___ \ | | | | |_ _| / ___| - | | / /_\ \ | |_/ / | | | | | | \ `--. - | | | _ | | / | | | | | | `--. \ -/\__/ / _ | | | | _ | |\ \ _ \ \_/ / _ _| |_ _ /\__/ / _ -\____/ (_)\_| |_/(_)\_| \_|(_) \___/ (_) \___/ (_)\____/ (_) - -""" - -logo_epic = r""" -_________ _______ _______ _________ _______ -\__ _/ ( ___ ) ( ____ ) |\ /| \__ __/ ( ____ \ - ) ( | ( ) | | ( )| | ) ( | ) ( | ( \/ - | | | (___) | | (____)| | | | | | | | (_____ - | | | ___ | | __) ( ( ) ) | | (_____ ) - | | | ( ) | | (\ ( \ \_/ / | | ) | -|\_) ) _ | ) ( | _ | ) \ \__ _ \ / _ ___) (___ _ /\____) | _ -(____/ (_)|/ \|(_)|/ \__/(_) \_/ (_)\_______/(_)\_______)(_) - -""" - -logo_ivrit = r""" - _ _ ____ __ __ ___ ____ - | | / \ | _ \ \ \ / / |_ _| / ___| - _ | | / _ \ | |_) | \ \ / / | | \___ \ - | |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _ - \___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_) - -""" - -logo_kban = r""" - - '||' . | . '||''|. . '||' '|' . '||' . .|'''.| . - || ||| || || '|. .' || ||.. ' - || | || ||''|' || | || ''|||. - || .''''|. || |. ||| || . '|| -|| .|' .|. .||. .||. '|' | .||. |'....|' - ''' - -""" - -logo_larry3d = r""" - - _____ ______ ____ __ __ ______ ____ -/\___ \ /\ _ \ /\ _`\ /\ \/\ \ /\__ _\ /\ _`\ -\/__/\ \ \ \ \L\ \ \ \ \L\ \ \ \ \ \ \ \/_/\ \/ \ \,\L\_\ - _\ \ \ \ \ __ \ \ \ , / \ \ \ \ \ \ \ \ \/_\__ \ - /\ \_\ \ __ \ \ \/\ \ __ \ \ \\ \ __ \ \ \_/ \ __ \_\ \__ __ /\ \L\ \ __ - \ \____//\_\ \ \_\ \_\/\_\ \ \_\ \_\/\_\ \ `\___//\_\ /\_____\/\_\ \ `\____\/\_\ - \/___/ \/_/ \/_/\/_/\/_/ \/_/\/ /\/_/ `\/__/ \/_/ \/_____/\/_/ \/_____/\/_/ - -""" - -logo_slane = r""" - - __ ___ ____ _ __ ____ _____ - / / / | / __ \ | | / / / _/ / ___/ - __ / / / /| | / /_/ / | | / / / / \__ \ -/ /_/ / _ / ___ | _ / _, _/ _ | |/ / _ _/ / _ ___/ / _ -\____/ (_)/_/ |_|(_)/_/ |_| (_)|___/ (_)/___/ (_)/____/ (_) - -""" - -logo_standard = r""" - - _ _ ____ __ __ ___ ____ - | | / \ | _ \ \ \ / / |_ _| / ___| - _ | | / _ \ | |_) | \ \ / / | | \___ \ - | |_| | _ / ___ \ _ | _ < _ \ V / _ | | _ ___) | _ - \___/ (_) /_/ \_\ (_) |_| \_\ (_) \_/ (_) |___| (_) |____/ (_) - -""" - -logo_alligator = r""" - - ::::::::::: ::: ::::::::: ::: ::: ::::::::::: :::::::: - :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: - +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ - +#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++ - +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ -#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# -##### ### ### ### ### ### ### ### ### ### ########### ### ######## ### - -""" # noqa: E501 - -logo_alligator2 = r""" - -::::::::::: ::: ::::::::: ::: ::: ::::::::::: :::::::: - :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: - +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ - +#+ +#++:++#++: +#++:++#: +#+ +:+ +#+ +#++:++#++ - +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ -#+# #+# #+# #+# #+# #+# #+# #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# - ##### ### ### ### ### ### ### ### ### ### ########### ### ######## ### - -""" - - -def get_logo(lo: str) -> str: - """Get a logo.""" - if "logo_" not in lo: - lo = "logo_" + lo - return globals()[lo] if lo in globals() else logo_alligator2 From 0a9b8a3adcaf66549c95989bf1c82a2600dbddd2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 6 Feb 2022 01:28:20 -0700 Subject: [PATCH 075/365] Rework on_command, ref #108 --- jarvis/cogs/modlog/command.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py index 75d54b5..f846f4c 100644 --- a/jarvis/cogs/modlog/command.py +++ b/jarvis/cogs/modlog/command.py @@ -1,40 +1,30 @@ """J.A.R.V.I.S. ModlogCommandCog.""" -from discord import DMChannel -from discord.ext import commands -from discord_slash import SlashContext +from dis_snek import InteractionContext, Snake, listen +from dis_snek.models.discord.channel import DMChannel +from dis_snek.models.discord.embed import EmbedField from jarvis.db.models import Setting from jarvis.utils import build_embed -from jarvis.utils.field import Field -class ModlogCommandCog(commands.Cog): +class ModlogCommandCog(object): """J.A.R.V.I.S. ModlogCommandCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot + self.bot.add_listener(self.on_command) - @commands.Cog.listener() - async def on_slash_command(self, ctx: SlashContext) -> None: + @listen() + async def on_command(self, ctx: InteractionContext) -> None: """Process on_slash_command events.""" if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]: modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first() if modlog: - channel = ctx.guild.get_channel(modlog.value) + channel = await ctx.guild.get_channel(modlog.value) + args = " ".join(f"{k}:v" for k, v in ctx.kwargs.items()) fields = [ - Field("Command", ctx.name), + EmbedField(name="Command", value=f"{ctx.invoked_name} {args}", inline=False), ] - if ctx.kwargs: - kwargs_string = " ".join(f"{k}: {str(ctx.kwargs[k])}" for k in ctx.kwargs) - fields.append( - Field( - "Keyword Args", - kwargs_string, - False, - ) - ) - if ctx.subcommand_name: - fields.insert(1, Field("Subcommand", ctx.subcommand_name)) embed = build_embed( title="Command Invoked", description=f"{ctx.author.mention} invoked a command", From 3d2dc03d70fa833e732c8ac19d3ddb6aa006cae0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 9 Feb 2022 14:18:56 -0700 Subject: [PATCH 076/365] Lots of abstraction, ref #121 --- jarvis/__init__.py | 2 +- jarvis/cogs/admin/ban.py | 12 +- jarvis/cogs/admin/purge.py | 5 +- jarvis/cogs/admin/roleping.py | 16 +-- jarvis/cogs/autoreact.py | 15 +-- jarvis/cogs/ctc2.py | 5 +- jarvis/cogs/dev.py | 70 +++++------ jarvis/cogs/jokes.py | 5 +- jarvis/cogs/owner.py | 9 +- jarvis/cogs/rolegiver.py | 18 +-- jarvis/cogs/settings.py | 12 +- jarvis/cogs/starboard.py | 10 +- jarvis/cogs/twitter.py | 15 +-- jarvis/cogs/verify.py | 10 +- jarvis/events/member.py | 6 +- jarvis/events/message.py | 57 +++++---- jarvis/tasks/reminder.py | 23 ++-- jarvis/tasks/twitter.py | 43 +++---- jarvis/tasks/unban.py | 49 +++----- jarvis/tasks/unwarn.py | 20 ++-- jarvis/utils/__init__.py | 122 +------------------ jarvis/utils/cachecog.py | 3 +- poetry.lock | 212 +++++++++++++++++++++++----------- pyproject.toml | 4 +- 24 files changed, 352 insertions(+), 391 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 9766919..c1537ad 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -24,7 +24,7 @@ restart_ctx = None jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) -__version__ = "2.0.0a0" +__version__ = "2.0.0a1" @listen() diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 4f3cdf4..f1cd3a8 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -3,6 +3,7 @@ import re from datetime import datetime, timedelta from dis_snek import InteractionContext, Permissions, Snake +from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User @@ -13,9 +14,10 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Ban, Unban -from jarvis.db.models import Ban, Unban -from jarvis.utils import build_embed, find, find_all +from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog from jarvis.utils.permissions import admin_or_permissions @@ -242,7 +244,9 @@ class BanCog(CacheCog): # We take advantage of the previous checks to save CPU cycles if not discord_ban_info: if isinstance(user, int): - database_ban_info = Ban.objects(guild=ctx.guild.id, user=user, active=True).first() + database_ban_info = await Ban.find_one( + q(guild=ctx.guild.id, user=user, active=True) + ) else: search = { "guild": ctx.guild.id, @@ -251,7 +255,7 @@ class BanCog(CacheCog): } if discrim: search["discrim"] = discrim - database_ban_info = Ban.objects(**search).first() + database_ban_info = await Ban.find_one(q(**search)) if not discord_ban_info and not database_ban_info: await ctx.send(f"Unable to find user {orig_user}", ephemeral=True) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 78a2c46..4a893fb 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -7,8 +7,9 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Autopurge, Purge -from jarvis.db.models import Autopurge, Purge from jarvis.utils.permissions import admin_or_permissions @@ -72,7 +73,7 @@ class PurgeCog(Scale): await ctx.send("Delay must be < 5 minutes", ephemeral=True) return - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id).first() + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if autopurge: await ctx.send("Autopurge already exists.", ephemeral=True) return diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 4d5fb70..57e40dc 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. RolepingCog.""" from dis_snek import InteractionContext, Permissions, Scale +from dis_snek.client.utils.misc_utils import find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -10,9 +11,10 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Roleping -from jarvis.db.models import Roleping -from jarvis.utils import build_embed, find_all +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -25,7 +27,7 @@ class RolepingCog(Scale): @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_add(self, ctx: InteractionContext, role: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=role.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if roleping: await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return @@ -123,7 +125,7 @@ class RolepingCog(Scale): async def _roleping_bypass_user( self, ctx: InteractionContext, user: Member, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -164,7 +166,7 @@ class RolepingCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -203,7 +205,7 @@ class RolepingCog(Scale): async def _roleping_restore_user( self, ctx: InteractionContext, user: Member, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return @@ -232,7 +234,7 @@ class RolepingCog(Scale): async def _roleping_restore_role( self, ctx: InteractionContext, role: Role, rping: Role ) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=rping.id).first() + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) if not roleping: await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) return diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 78d403b..d5d127b 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -3,6 +3,7 @@ import re from typing import Optional, Tuple from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( OptionTypes, @@ -10,10 +11,10 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Autoreact from jarvis.data.unicode import emoji_list -from jarvis.db.models import Autoreact -from jarvis.utils import find from jarvis.utils.permissions import admin_or_permissions @@ -37,7 +38,7 @@ class AutoReactCog(Scale): Returns: Tuple of success? and error message """ - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if exists: return False, f"Autoreact already exists for {channel.mention}." @@ -93,10 +94,10 @@ class AutoReactCog(Scale): if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): await ctx.send("Please use a custom emote from this server.", ephemeral=True) return - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: self.create_autoreact(ctx, channel) - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", @@ -134,7 +135,7 @@ class AutoReactCog(Scale): async def _autoreact_remove( self, ctx: InteractionContext, channel: GuildText, emote: str ) -> None: - autoreact = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} {emote}", @@ -169,7 +170,7 @@ class AutoReactCog(Scale): required=True, ) async def _autoreact_list(self, ctx: InteractionContext, channel: GuildText) -> None: - exists = Autoreact.objects(guild=ctx.guild.id, channel=channel.id).first() + exists = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not exists: await ctx.send( f"Please create autoreact first with /autoreact add {channel.mention} ", diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index d509f0f..67f9d20 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -10,8 +10,9 @@ from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import slash_command from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets +from jarvis_core.db import q +from jarvis_core.db.models import Guess -from jarvis.db.models import Guess from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog @@ -74,7 +75,7 @@ class CTCCog(CacheCog): ephemeral=True, ) return - guessed = Guess.objects(guess=guess).first() + guessed = await Guess.find_one(q(guess=guess)) if guessed: await ctx.send("Already guessed, dipshit.", ephemeral=True) return diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 98f2db2..ab26c05 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -4,12 +4,12 @@ import hashlib import re import subprocess # noqa: S404 import uuid as uuidpy -from typing import Any, Union import ulid as ulidpy from bson import ObjectId from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.message import Attachment from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -18,8 +18,11 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets +from jarvis_core.filters import invites, url +from jarvis_core.util import convert_bytesize, hash +from jarvis_core.util.http import get_size -from jarvis.utils import build_embed, convert_bytesize +from jarvis.utils import build_embed supported_hashes = {x for x in hashlib.algorithms_guaranteed if "shake" not in x} @@ -36,29 +39,9 @@ UUID_VERIFY = re.compile( re.IGNORECASE, ) -invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 - flags=re.IGNORECASE, -) - UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5} - -def hash_obj(hash: Any, data: Union[str, bytes], text: bool = True) -> str: - """Hash data with hash object. - - Data can be text or binary - """ - if text: - hash.update(data.encode("UTF-8")) - return hash.hexdigest() - BSIZE = 65536 - block_idx = 0 - while block_idx * BSIZE < len(data): - block = data[BSIZE * block_idx : BSIZE * (block_idx + 1)] - hash.update(block) - block_idx += 1 - return hash.hexdigest() +MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): @@ -76,26 +59,47 @@ class DevCog(Scale): name="data", description="Data to hash", opt_type=OptionTypes.STRING, - required=True, + required=False, + ) + @slash_option( + name="attach", description="File to hash", opt_type=OptionTypes.ATTACHMENT, required=False ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) - async def _hash(self, ctx: InteractionContext, method: str, data: str) -> None: - if not data: + async def _hash( + self, ctx: InteractionContext, method: str, data: str = None, attach: Attachment = None + ) -> None: + if not data and not attach: await ctx.send( "No data to hash", ephemeral=True, ) return - text = True - # Default to sha256, just in case - hash = getattr(hashlib, method, hashlib.sha256)() - hex = hash_obj(hash, data, text) - data_size = convert_bytesize(len(data)) - title = data if text else ctx.message.attachments[0].filename + if data and invites.match(data): + await ctx.send("No hashing invites", ephemeral=True) + return + title = data + if attach: + data = attach.url + title = attach.filename + elif url.match(data): + if await get_size(data) > MAX_FILESIZE: + await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + return + title = data.split("/")[-1] + + await ctx.defer() + try: + hexstr, size, c_type = await hash(data, method) + except Exception as e: + await ctx.send(f"Failed to hash data: ```\n{e}\n```", ephemeral=True) + return + + data_size = convert_bytesize(size) description = "Hashed using " + method fields = [ + EmbedField("Content Type", c_type, False), EmbedField("Data Size", data_size, False), - EmbedField("Hash", f"`{hex}`", False), + EmbedField("Hash", f"`{hexstr}`", False), ] embed = build_embed(title=title, description=description, fields=fields) diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py index 0bf3fcc..7697d37 100644 --- a/jarvis/cogs/jokes.py +++ b/jarvis/cogs/jokes.py @@ -14,8 +14,9 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets +from jarvis_core.db import q +from jarvis_core.db.models import Joke -from jarvis.db.models import Joke from jarvis.utils import build_embed @@ -46,7 +47,7 @@ class JokeCog(Scale): threshold = 500 # Minimum score result = None if id: - result = Joke.objects(rid=id).first() + result = await Joke.find_one(q(rid=id)) else: pipeline = [ {"$match": {"score": {"$gt": threshold}}}, diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py index c00118e..094faf4 100644 --- a/jarvis/cogs/owner.py +++ b/jarvis/cogs/owner.py @@ -4,8 +4,7 @@ from dis_snek.models.discord.user import User from dis_snek.models.snek.checks import is_owner from dis_snek.models.snek.command import check -from jarvis.config import reload_config -from jarvis.db.models import Config +from jarvis import jconfig class OwnerCog(Scale): @@ -17,7 +16,7 @@ class OwnerCog(Scale): def __init__(self, bot: Snake): self.bot = bot - self.admins = Config.objects(key="admins").first() + # self.admins = await Config.find_one(q(key="admins")) @message_command(name="addadmin") @check(is_owner()) @@ -27,7 +26,7 @@ class OwnerCog(Scale): return self.admins.value.append(user.id) self.admins.save() - reload_config() + jconfig.reload() await ctx.send(f"{user.mention} is now an admin. Use this power carefully.") @message_command(name="deladmin") @@ -38,7 +37,7 @@ class OwnerCog(Scale): return self.admins.value.remove(user.id) self.admins.save() - reload_config() + jconfig.reload() await ctx.send(f"{user.mention} is no longer an admin.") diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 8b87241..7a115b9 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -2,6 +2,7 @@ import asyncio from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role @@ -12,9 +13,10 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import check, cooldown from dis_snek.models.snek.cooldowns import Buckets +from jarvis_core.db import q +from jarvis_core.db.models import Rolegiver -from jarvis.db.models import Rolegiver -from jarvis.utils import build_embed, get +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -30,7 +32,7 @@ class RolegiverCog(Scale): @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if setting and role.id in setting.roles: await ctx.send("Role already in rolegiver", ephemeral=True) return @@ -80,7 +82,7 @@ class RolegiverCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_remove(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -162,7 +164,7 @@ class RolegiverCog(Scale): @slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -198,7 +200,7 @@ class RolegiverCog(Scale): @slash_command(name="role", sub_cmd_name="get", sub_cmd_description="Get a role") @cooldown(bucket=Buckets.USER, rate=1, interval=10) async def _role_get(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -276,7 +278,7 @@ class RolegiverCog(Scale): async def _role_remove(self, ctx: InteractionContext) -> None: user_roles = ctx.author.roles - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): await ctx.send("Rolegiver has no roles", ephemeral=True) return @@ -355,7 +357,7 @@ class RolegiverCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: - setting = Rolegiver.objects(guild=ctx.guild.id).first() + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or not setting.roles: await ctx.send("Rolegiver has no roles", ephemeral=True) guild_role_ids = [r.id for r in ctx.guild.roles] diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 52da039..d761bc1 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -7,6 +7,7 @@ from discord.ext import commands from discord.utils import find from discord_slash import SlashContext, cog_ext from discord_slash.utils.manage_commands import create_option +from jarvis_core.db import q from jarvis.db.models import Setting from jarvis.utils import build_embed @@ -20,9 +21,9 @@ class SettingsCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - def update_settings(self, setting: str, value: Any, guild: int) -> bool: + async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" - existing = Setting.objects(setting=setting, guild=guild).first() + existing = await Setting.find_one(q(setting=setting, guild=guild)) if not existing: existing = Setting(setting=setting, guild=guild, value=value) existing.value = value @@ -30,9 +31,12 @@ class SettingsCog(commands.Cog): return updated is not None - def delete_settings(self, setting: str, guild: int) -> bool: + async def delete_settings(self, setting: str, guild: int) -> bool: """Delete a guild setting.""" - return Setting.objects(setting=setting, guild=guild).delete() + existing = await Setting.find_one(q(setting=setting, guild=guild)) + if existing: + return await existing.delete() + return False @cog_ext.cog_subcommand( base="settings", diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 79442e6..a70d03a 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Starboard Cog.""" from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.message import Message @@ -11,9 +12,10 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Star, Starboard -from jarvis.db.models import Star, Starboard -from jarvis.utils import build_embed, find +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions supported_images = [ @@ -64,7 +66,7 @@ class StarboardCog(Scale): await ctx.send("Channel must be a GuildText", ephemeral=True) return - exists = Starboard.objects(channel=channel.id, guild=ctx.guild.id).first() + exists = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id)) if exists: await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True) return @@ -248,7 +250,7 @@ class StarboardCog(Scale): if not isinstance(starboard, GuildText): await ctx.send("Channel must be a GuildText channel", ephemeral=True) return - exists = Starboard.objects(channel=starboard.id, guild=ctx.guild.id).first() + exists = await Starboard.find_one(q(channel=starboard.id, guild=ctx.guild.id)) if not exists: await ctx.send( f"Starboard does not exist in {starboard.mention}. Please create it first", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index bd92ddc..7d16b92 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -13,9 +13,10 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Twitter -from jarvis.config import get_config -from jarvis.db.models import Twitter +from jarvis import jconfig from jarvis.utils.permissions import admin_or_permissions @@ -24,7 +25,7 @@ class TwitterCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = get_config() + config = jconfig auth = tweepy.AppAuthHandler( config.twitter["consumer_key"], config.twitter["consumer_secret"] ) @@ -75,12 +76,12 @@ class TwitterCog(Scale): ) return - count = Twitter.objects(guild=ctx.guild.id).count() + count = len([i async for i in Twitter.find(guild=ctx.guild.id)]) if count >= 12: await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) return - exists = Twitter.objects(twitter_id=account.id, guild=ctx.guild.id) + exists = Twitter.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) if exists: await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return @@ -95,7 +96,7 @@ class TwitterCog(Scale): retweets=retweets, ) - t.save() + await t.commit() await ctx.send(f"Now following `@{handle}` in {channel.mention}") @@ -194,7 +195,7 @@ class TwitterCog(Scale): handlemap = {str(x.id): x.handle for x in twitters} for to_update in context.context.values: - t = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_update)).first() + t = await Twitter.find_one(q(guild=ctx.guild.id, id=ObjectId(to_update)))() t.retweets = retweets t.save() diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 2d1db38..2255623 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -7,8 +7,8 @@ from dis_snek.models.application_commands import slash_command from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets - -from jarvis.db.models import Setting +from jarvis_core.db import q +from jarvis_core.db.models import Setting def create_layout() -> list: @@ -39,7 +39,7 @@ class VerifyCog(Scale): @cooldown(bucket=Buckets.USER, rate=1, interval=15) async def _verify(self, ctx: InteractionContext) -> None: await ctx.defer() - role = Setting.objects(guild=ctx.guild.id, setting="verified").first() + role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) if not role: await ctx.send("This guild has not enabled verification", delete_after=5) return @@ -62,10 +62,10 @@ class VerifyCog(Scale): for row in components: for component in row.components: component.disabled = True - setting = Setting.objects(guild=ctx.guild.id, setting="verified").first() + setting = await Setting.find_one(guild=ctx.guild.id, setting="verified") role = await ctx.guild.get_role(setting.value) await ctx.author.add_roles(role, reason="Verification passed") - setting = Setting.objects(guild=ctx.guild.id, setting="unverified").first() + setting = await Setting.find_one(guild=ctx.guild.id, setting="unverified") if setting: role = await ctx.guild.get_role(setting.value) await ctx.author.remove_roles(role, reason="Verification passed") diff --git a/jarvis/events/member.py b/jarvis/events/member.py index 44a42c4..f7e4ada 100644 --- a/jarvis/events/member.py +++ b/jarvis/events/member.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. Member event handler.""" from dis_snek import Snake, listen from dis_snek.models.discord.user import Member - -from jarvis.db.models import Setting +from jarvis_core.db import q +from jarvis_core.db.models import Setting class MemberEventHandler(object): @@ -16,7 +16,7 @@ class MemberEventHandler(object): async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild - unverified = Setting.objects(guild=guild.id, setting="unverified").first() + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: role = guild.get_role(unverified.value) if role not in user.roles: diff --git a/jarvis/events/message.py b/jarvis/events/message.py index 0433cc4..693bc3c 100644 --- a/jarvis/events/message.py +++ b/jarvis/events/message.py @@ -2,13 +2,15 @@ import re from dis_snek import Snake, listen +from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.message import Message +from jarvis_core.db import q +from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.config import get_config -from jarvis.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis.utils import build_embed, find, find_all +import jarvis +from jarvis.utils import build_embed invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 @@ -26,16 +28,18 @@ class MessageEventHandler(object): async def autopurge(self, message: Message) -> None: """Handle autopurge events.""" - autopurge = Autopurge.objects(guild=message.guild.id, channel=message.channel.id).first() + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) if autopurge: await message.delete(delay=autopurge.delay) async def autoreact(self, message: Message) -> None: """Handle autoreact events.""" - autoreact = Autoreact.objects( - guild=message.guild.id, - channel=message.channel.id, - ).first() + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) if autoreact: for reaction in autoreact.reactions: await message.add_reaction(reaction) @@ -50,10 +54,10 @@ class MessageEventHandler(object): ) content = re.sub(r"\s+", "", message.content) match = invites.search(content) - setting = Setting.objects(guild=message.guild.id, setting="noinvite").first() + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) if not setting: setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - setting.save() + await setting.commit() if match: guild_invites = await message.guild.invites() allowed = [x.code for x in guild_invites] + [ @@ -63,14 +67,15 @@ class MessageEventHandler(object): ] if match.group(1) not in allowed and setting.value: await message.delete() - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Sent an invite link", user=message.author.id, - ).save() + ) + await w.commit() fields = [ EmbedField( name="Reason", @@ -94,10 +99,12 @@ class MessageEventHandler(object): async def massmention(self, message: Message) -> None: """Handle massmention events.""" - massmention = Setting.objects( - guild=message.guild.id, - setting="massmention", - ).first() + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) if ( massmention and massmention.value > 0 # noqa: W503 @@ -105,14 +112,15 @@ class MessageEventHandler(object): - (1 if message.author in message.mentions else 0) # noqa: W503 > massmention.value # noqa: W503 ): - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Mass Mention", user=message.author.id, - ).save() + ) + await w.commit() fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] embed = build_embed( title="Warning", @@ -130,7 +138,7 @@ class MessageEventHandler(object): async def roleping(self, message: Message) -> None: """Handle roleping events.""" - rolepings = Roleping.objects(guild=message.guild.id, active=True) + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) if not rolepings: return @@ -169,14 +177,15 @@ class MessageEventHandler(object): break if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - _ = Warning( + w = Warning( active=True, - admin=get_config().client_id, + admin=jarvis.jconfig.client_id, duration=24, guild=message.guild.id, reason="Pinged a blocked role/user with a blocked role", user=message.author.id, - ).save() + ) + await w.commit() fields = [ EmbedField( name="Reason", diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index b897e8e..98385ab 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -1,23 +1,24 @@ """J.A.R.V.I.S. reminder background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Reminder import jarvis -from jarvis.db.models import Reminder from jarvis.utils import build_embed -async def _remind() -> None: - """J.A.R.V.I.S. reminder blocking task.""" - reminders = Reminder.objects(remind_at__lte=datetime.utcnow() + timedelta(seconds=30)) - for reminder in reminders: +@Task.create(trigger=IntervalTrigger(seconds=15)) +async def remind() -> None: + """J.A.R.V.I.S. reminder background task.""" + reminders = Reminder.find(q(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))) + async for reminder in reminders: if reminder.remind_at <= datetime.utcnow(): user = await jarvis.jarvis.fetch_user(reminder.user) if not user: - reminder.delete() + await reminder.delete() continue embed = build_embed( title="You have a reminder", @@ -41,10 +42,4 @@ async def _remind() -> None: "but I couldn't send it to you." ) finally: - reminder.delete() - - -@Task.create(trigger=IntervalTrigger(seconds=15)) -async def remind() -> None: - """J.A.R.V.I.S. reminder background task.""" - await to_thread(_remind) + await reminder.delete() diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py index 46638f5..ff85def 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -1,41 +1,40 @@ """J.A.R.V.I.S. twitter background task handler.""" import logging -from asyncio import to_thread from datetime import datetime, timedelta import tweepy from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Twitter import jarvis -from jarvis.config import get_config -from jarvis.db.models import Twitter logger = logging.getLogger("jarvis") -__auth = tweepy.AppAuthHandler( - get_config().twitter["consumer_key"], get_config().twitter["consumer_secret"] -) -__api = tweepy.API(__auth) - -async def _tweets() -> None: - """J.A.R.V.I.S. twitter blocking task.""" +@Task.create(trigger=IntervalTrigger(minutes=1)) +async def tweets() -> None: + """J.A.R.V.I.S. twitter background task.""" + config = jarvis.config.get_config() + __auth = tweepy.AppAuthHandler( + config.twitter["consumer_key"], jarvis.jconfig.twitter["consumer_secret"] + ) + __api = tweepy.API(__auth) guild_cache = {} channel_cache = {} - twitters = Twitter.objects(active=True) - for twitter in twitters: + twitters = Twitter.find(q(active=True)) + async for twitter in twitters: try: if not twitter.twitter_id or not twitter.last_sync: user = __api.get_user(screen_name=twitter.handle) - twitter.twitter_id = user.id - twitter.handle = user.screen_name - twitter.last_sync = datetime.now() + twitter.update( + q(twitter_id=user.id, handle=user.screen_name, last_sync=datetime.now()) + ) if twitter.last_sync + timedelta(hours=1) <= datetime.now(): user = __api.get_user(id=twitter.twitter_id) - twitter.handle = user.screen_name - twitter.last_sync = datetime.now() + twitter.update(q(handle=user.screen_name, last_sync=datetime.now())) if tweets := __api.user_timeline(id=twitter.twitter_id): guild_id = twitter.guild @@ -58,13 +57,7 @@ async def _tweets() -> None: f"`@{twitter.handle}` {verb}tweeted this at : {url}" ) newest = max(tweets, key=lambda x: x.id) - twitter.last_tweet = newest.id - twitter.save() + twitter.update(q(last_tweet=newest.id)) + await twitter.commit() except Exception as e: logger.error(f"Error with tweets: {e}") - - -@Task.create(trigger=IntervalTrigger(minutes=1)) -async def tweets() -> None: - """J.A.R.V.I.S. twitter background task.""" - await to_thread(_tweets) diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index f5b2849..70c2b60 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -1,46 +1,33 @@ """J.A.R.V.I.S. unban background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Ban, Unban import jarvis -from jarvis.config import get_config -from jarvis.db.models import Ban, Unban - -jarvis_id = get_config().client_id +@Task.create(IntervalTrigger(minutes=10)) async def _unban() -> None: - """J.A.R.V.I.S. unban blocking task.""" - bans = Ban.objects(type="temp", active=True) - unbans = [] - for ban in bans: - if ban.created_at + timedelta(hours=ban.duration) < datetime.utcnow() + timedelta( - minutes=10 - ): + """J.A.R.V.I.S. unban background task.""" + jarvis_id = jarvis.jconfig.client_id + bans = Ban.find(q(type="temp", active=True)) + async for ban in bans: + if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): guild = await jarvis.jarvis.get_guild(ban.guild) user = await jarvis.jarvis.get_user(ban.user) if user: await guild.unban(user=user, reason="Ban expired") - ban.active = False - ban.save() - unbans.append( - Unban( - user=user.id, - guild=guild.id, - username=user.name, - discrim=user.discriminator, - admin=jarvis_id, - reason="Ban expired", - ) + ban.update(q(active=False)) + await ban.commit() + u = Unban( + user=user.id, + guild=guild.id, + username=user.name, + discrim=user.discriminator, + admin=jarvis_id, + reason="Ban expired", ) - if unbans: - Unban.objects().insert(unbans) - - -@Task.create(IntervalTrigger(minutes=10)) -async def unban() -> None: - """J.A.R.V.I.S. unban background task.""" - await to_thread(_unban) + await u.commit() diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py index 0af40a1..c3afdac 100644 --- a/jarvis/tasks/unwarn.py +++ b/jarvis/tasks/unwarn.py @@ -1,23 +1,17 @@ """J.A.R.V.I.S. unwarn background task handler.""" -from asyncio import to_thread from datetime import datetime, timedelta from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger - -from jarvis.db.models import Warning - - -async def _unwarn() -> None: - """J.A.R.V.I.S. unwarn blocking task.""" - warns = Warning.objects(active=True) - for warn in warns: - if warn.created_at + timedelta(hours=warn.duration) < datetime.utcnow(): - warn.active = False - warn.save() +from jarvis_core.db import q +from jarvis_core.db.models import Warning @Task.create(IntervalTrigger(hours=1)) async def unwarn() -> None: """J.A.R.V.I.S. unwarn background task.""" - await to_thread(_unwarn) + warns = Warning.find(q(active=True)) + async for warn in warns: + if warn.created_at + timedelta(hours=warn.duration) < datetime.now(): + warn.update(q(active=False)) + await warn.commit() diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index c5d8b19..009d24b 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,7 +1,6 @@ """J.A.R.V.I.S. Utility Functions.""" from datetime import datetime from pkgutil import iter_modules -from typing import Any, Callable, Iterable, List, Optional, TypeVar import git from dis_snek.models.discord.embed import Embed @@ -10,9 +9,7 @@ import jarvis.cogs import jarvis.db from jarvis.config import get_config -__all__ = ["field", "db", "cachecog", "permissions"] - -T = TypeVar("T") +__all__ = ["cachecog", "permissions"] def build_embed( @@ -38,27 +35,6 @@ def build_embed( return embed -def convert_bytesize(b: int) -> str: - """Convert bytes amount to human readable.""" - b = float(b) - sizes = ["B", "KB", "MB", "GB", "TB", "PB"] - size = 0 - while b >= 1024 and size < len(sizes) - 1: - b = b / 1024 - size += 1 - return "{:0.3f} {}".format(b, sizes[size]) - - -def unconvert_bytesize(size: int, ending: str) -> int: - """Convert human readable to bytes.""" - ending = ending.upper() - sizes = ["B", "KB", "MB", "GB", "TB", "PB"] - if ending == "B": - return size - # Rounding is only because bytes cannot be partial - return round(size * (1024 ** sizes.index(ending))) - - def get_extensions(path: str = jarvis.cogs.__path__) -> list: """Get J.A.R.V.I.S. cogs.""" config = get_config() @@ -85,99 +61,3 @@ def get_repo_hash() -> str: """J.A.R.V.I.S. current branch hash.""" repo = git.Repo(".") return repo.head.object.hexsha - - -def find(predicate: Callable, sequence: Iterable) -> Optional[Any]: - """ - Find the first element in a sequence that matches the predicate. - - ??? Hint "Example Usage:" - ```python - member = find(lambda m: m.name == "UserName", guild.members) - ``` - Args: - predicate: A callable that returns a boolean value - sequence: A sequence to be searched - - Returns: - A match if found, otherwise None - - """ - for el in sequence: - if predicate(el): - return el - return None - - -def find_all(predicate: Callable, sequence: Iterable) -> List[Any]: - """ - Find all elements in a sequence that match the predicate. - - ??? Hint "Example Usage:" - ```python - members = find_all(lambda m: m.name == "UserName", guild.members) - ``` - Args: - predicate: A callable that returns a boolean value - sequence: A sequence to be searched - - Returns: - A list of matches - - """ - return [el for el in sequence if predicate(el)] - - -def get(sequence: Iterable, **kwargs: Any) -> Optional[Any]: - """ - Find the first element in a sequence that matches all attrs. - - ??? Hint "Example Usage:" - ```python - channel = get(guild.channels, nsfw=False, category="General") - ``` - - Args: - sequence: A sequence to be searched - kwargs: Keyword arguments to search the sequence for - - Returns: - A match if found, otherwise None - """ - if not kwargs: - return sequence[0] - - for el in sequence: - if any(not hasattr(el, attr) for attr in kwargs.keys()): - continue - if all(getattr(el, attr) == value for attr, value in kwargs.items()): - return el - return None - - -def get_all(sequence: Iterable, **kwargs: Any) -> List[Any]: - """ - Find all elements in a sequence that match all attrs. - - ??? Hint "Example Usage:" - ```python - channels = get_all(guild.channels, nsfw=False, category="General") - ``` - - Args: - sequence: A sequence to be searched - kwargs: Keyword arguments to search the sequence for - - Returns: - A list of matches - """ - if not kwargs: - return sequence - - matches = [] - for el in sequence: - if any(not hasattr(el, attr) for attr in kwargs.keys()): - continue - if all(getattr(el, attr) == value for attr, value in kwargs.items()): - matches.append(el) - return matches diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py index 9497bac..d7be74b 100644 --- a/jarvis/utils/cachecog.py +++ b/jarvis/utils/cachecog.py @@ -2,11 +2,10 @@ from datetime import datetime, timedelta from dis_snek import InteractionContext, Scale, Snake +from dis_snek.client.utils.misc_utils import find from dis_snek.ext.tasks.task import Task from dis_snek.ext.tasks.triggers import IntervalTrigger -from jarvis.utils import find - class CacheCog(Scale): """Cog wrapper for command caching.""" diff --git a/poetry.lock b/poetry.lock index f59cc4b..af0717b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -136,7 +136,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dis-snek" -version = "5.0.0" +version = "6.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -212,6 +212,28 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] +[[package]] +name = "jarvis-core" +version = "0.2.1" +description = "" +category = "main" +optional = false +python-versions = "^3.10" +develop = false + +[package.dependencies] +dis-snek = "*" +motor = "^2.5.1" +orjson = "^3.6.6" +PyYAML = "^6.0" +umongo = "^3.1.0" + +[package.source] +type = "git" +url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" +reference = "main" +resolved_reference = "0e627eae725abb1e6f3766c5dc94bd80d0ac6702" + [[package]] name = "jedi" version = "0.18.1" @@ -235,6 +257,20 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "marshmallow" +version = "3.14.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.6.1" @@ -254,6 +290,20 @@ python-versions = ">=3.6" [package.dependencies] pymongo = ">=3.4,<4.0" +[[package]] +name = "motor" +version = "2.5.1" +description = "Non-blocking MongoDB driver for Tornado or asyncio" +category = "main" +optional = false +python-versions = ">=3.5.2" + +[package.dependencies] +pymongo = ">=3.12,<4" + +[package.extras] +encryption = ["pymongo[encryption] (>=3.12,<4)"] + [[package]] name = "multidict" version = "6.0.2" @@ -272,7 +322,7 @@ python-versions = "*" [[package]] name = "numpy" -version = "1.22.1" +version = "1.22.2" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -337,7 +387,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pillow" -version = "9.0.0" +version = "9.0.1" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -345,7 +395,7 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -597,7 +647,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "main" optional = false @@ -637,6 +687,23 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "umongo" +version = "3.1.0" +description = "sync/async MongoDB ODM, yes." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +marshmallow = ">=3.10.0" +pymongo = ">=3.7.0" + +[package.extras] +mongomock = ["mongomock"] +motor = ["motor (>=2.0,<3.0)"] +txmongo = ["txmongo (>=19.2.0)"] + [[package]] name = "urllib3" version = "1.26.8" @@ -681,7 +748,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "8a1e6e29ff70363abddad36082a494c4ce1f9cc672fe7aff30b6d5b596d50dac" +content-hash = "d34963008bb31a5168210290f44909aa684a363455b2d59fe792f41918ec4705" [metadata.files] aiohttp = [ @@ -820,8 +887,8 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dis-snek = [ - {file = "dis-snek-5.0.0.tar.gz", hash = "sha256:cc733b510d6b20523a8067f19b6d9b99804c13e37d78cd6e0fc401098adbca27"}, - {file = "dis_snek-5.0.0-py3-none-any.whl", hash = "sha256:d1d50ba468ad6b0788e9281eb9d83f6eb2f8d964c1212ccd0e3fb33295462263"}, + {file = "dis-snek-6.0.0.tar.gz", hash = "sha256:3abe2af832bd87adced01ebc697e418b25871a4158ac8ba2e202d8fbb2921f44"}, + {file = "dis_snek-6.0.0-py3-none-any.whl", hash = "sha256:106cb08b0cc982c59db31be01ee529e4d7273835de7f418562d8e6b24d7f965d"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -904,6 +971,7 @@ isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] +jarvis-core = [] jedi = [ {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, @@ -947,6 +1015,10 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] +marshmallow = [ + {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, + {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -955,6 +1027,10 @@ mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, ] +motor = [ + {file = "motor-2.5.1-py3-none-any.whl", hash = "sha256:961fdceacaae2c7236c939166f66415be81be8bbb762da528386738de3a0f509"}, + {file = "motor-2.5.1.tar.gz", hash = "sha256:663473f4498f955d35db7b6f25651cb165514c247136f368b84419cb7635f6b8"}, +] multidict = [ {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, @@ -1021,28 +1097,25 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] numpy = [ - {file = "numpy-1.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d62d6b0870b53799204515145935608cdeb4cebb95a26800b6750e48884cc5b"}, - {file = "numpy-1.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:831f2df87bd3afdfc77829bc94bd997a7c212663889d56518359c827d7113b1f"}, - {file = "numpy-1.22.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d1563060e77096367952fb44fca595f2b2f477156de389ce7c0ade3aef29e21"}, - {file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69958735d5e01f7b38226a6c6e7187d72b7e4d42b6b496aca5860b611ca0c193"}, - {file = "numpy-1.22.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45a7dfbf9ed8d68fd39763940591db7637cf8817c5bce1a44f7b56c97cbe211e"}, - {file = "numpy-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:7e957ca8112c689b728037cea9c9567c27cf912741fabda9efc2c7d33d29dfa1"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:800dfeaffb2219d49377da1371d710d7952c9533b57f3d51b15e61c4269a1b5b"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:65f5e257987601fdfc63f1d02fca4d1c44a2b85b802f03bd6abc2b0b14648dd2"}, - {file = "numpy-1.22.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:632e062569b0fe05654b15ef0e91a53c0a95d08ffe698b66f6ba0f927ad267c2"}, - {file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d245a2bf79188d3f361137608c3cd12ed79076badd743dc660750a9f3074f7c"}, - {file = "numpy-1.22.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b4018a19d2ad9606ce9089f3d52206a41b23de5dfe8dc947d2ec49ce45d015"}, - {file = "numpy-1.22.1-cp38-cp38-win32.whl", hash = "sha256:f8ad59e6e341f38266f1549c7c2ec70ea0e3d1effb62a44e5c3dba41c55f0187"}, - {file = "numpy-1.22.1-cp38-cp38-win_amd64.whl", hash = "sha256:60f19c61b589d44fbbab8ff126640ae712e163299c2dd422bfe4edc7ec51aa9b"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2db01d9838a497ba2aa9a87515aeaf458f42351d72d4e7f3b8ddbd1eba9479f2"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bcd19dab43b852b03868796f533b5f5561e6c0e3048415e675bec8d2e9d286c1"}, - {file = "numpy-1.22.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78bfbdf809fc236490e7e65715bbd98377b122f329457fffde206299e163e7f3"}, - {file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c51124df17f012c3b757380782ae46eee85213a3215e51477e559739f57d9bf6"}, - {file = "numpy-1.22.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d54b7b516f0ca38a69590557814de2dd638d7d4ed04864826acaac5ebb8f01"}, - {file = "numpy-1.22.1-cp39-cp39-win32.whl", hash = "sha256:b5ec9a5eaf391761c61fd873363ef3560a3614e9b4ead17347e4deda4358bca4"}, - {file = "numpy-1.22.1-cp39-cp39-win_amd64.whl", hash = "sha256:4ac4d7c9f8ea2a79d721ebfcce81705fc3cd61a10b731354f1049eb8c99521e8"}, - {file = "numpy-1.22.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e60ef82c358ded965fdd3132b5738eade055f48067ac8a5a8ac75acc00cad31f"}, - {file = "numpy-1.22.1.zip", hash = "sha256:e348ccf5bc5235fc405ab19d53bec215bb373300e5523c7b476cc0da8a5e9973"}, + {file = "numpy-1.22.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:515a8b6edbb904594685da6e176ac9fbea8f73a5ebae947281de6613e27f1956"}, + {file = "numpy-1.22.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76a4f9bce0278becc2da7da3b8ef854bed41a991f4226911a24a9711baad672c"}, + {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:168259b1b184aa83a514f307352c25c56af111c269ffc109d9704e81f72e764b"}, + {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3556c5550de40027d3121ebbb170f61bbe19eb639c7ad0c7b482cd9b560cd23b"}, + {file = "numpy-1.22.2-cp310-cp310-win_amd64.whl", hash = "sha256:aafa46b5a39a27aca566198d3312fb3bde95ce9677085efd02c86f7ef6be4ec7"}, + {file = "numpy-1.22.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:55535c7c2f61e2b2fc817c5cbe1af7cb907c7f011e46ae0a52caa4be1f19afe2"}, + {file = "numpy-1.22.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:60cb8e5933193a3cc2912ee29ca331e9c15b2da034f76159b7abc520b3d1233a"}, + {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b536b6840e84c1c6a410f3a5aa727821e6108f3454d81a5cd5900999ef04f89"}, + {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2638389562bda1635b564490d76713695ff497242a83d9b684d27bb4a6cc9d7a"}, + {file = "numpy-1.22.2-cp38-cp38-win32.whl", hash = "sha256:6767ad399e9327bfdbaa40871be4254d1995f4a3ca3806127f10cec778bd9896"}, + {file = "numpy-1.22.2-cp38-cp38-win_amd64.whl", hash = "sha256:03ae5850619abb34a879d5f2d4bb4dcd025d6d8fb72f5e461dae84edccfe129f"}, + {file = "numpy-1.22.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d76a26c5118c4d96e264acc9e3242d72e1a2b92e739807b3b69d8d47684b6677"}, + {file = "numpy-1.22.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15efb7b93806d438e3bc590ca8ef2f953b0ce4f86f337ef4559d31ec6cf9d7dd"}, + {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badca914580eb46385e7f7e4e426fea6de0a37b9e06bec252e481ae7ec287082"}, + {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dd11d9f13ea1be17bac39c1942f527cbf7065f94953cf62dfe805653da2f8f"}, + {file = "numpy-1.22.2-cp39-cp39-win32.whl", hash = "sha256:8cf33634b60c9cef346663a222d9841d3bbbc0a2f00221d6bcfd0d993d5543f6"}, + {file = "numpy-1.22.2-cp39-cp39-win_amd64.whl", hash = "sha256:59153979d60f5bfe9e4c00e401e24dfe0469ef8da6d68247439d3278f30a180f"}, + {file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"}, + {file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"}, ] oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, @@ -1092,42 +1165,45 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pillow = [ - {file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"}, - {file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"}, - {file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"}, - {file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"}, - {file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"}, - {file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"}, - {file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"}, - {file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"}, - {file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"}, - {file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"}, - {file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"}, - {file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"}, - {file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"}, + {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, + {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, + {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, + {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -1368,8 +1444,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tweepy = [ {file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"}, @@ -1431,6 +1507,10 @@ ulid-py = [ {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, {file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"}, ] +umongo = [ + {file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"}, + {file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"}, +] urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, diff --git a/pyproject.toml b/pyproject.toml index 7eee61c..a897256 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ authors = ["Zevaryx "] [tool.poetry.dependencies] python = "^3.10" PyYAML = "^6.0" -dis-snek = "^5.0.0" +dis-snek = "*" GitPython = "^3.1.26" mongoengine = "^0.23.1" opencv-python = "^4.5.5" @@ -17,6 +17,8 @@ python-gitlab = "^3.1.1" ulid-py = "^1.1.0" tweepy = "^4.5.0" orjson = "^3.6.6" +jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} +aiohttp = "^3.8.1" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 38c0a470860954cd56ee076db57539e9b82320b6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 16 Feb 2022 11:09:17 -0700 Subject: [PATCH 077/365] Fix dev cog, add error logging --- jarvis/__init__.py | 90 +++++++++++++++++++++++++++++++++++--------- jarvis/cogs/dev.py | 8 +++- jarvis/cogs/error.py | 56 --------------------------- 3 files changed, 79 insertions(+), 75 deletions(-) delete mode 100644 jarvis/cogs/error.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index c1537ad..733cdbb 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,8 +1,12 @@ """Main J.A.R.V.I.S. package.""" +import json import logging +import traceback +from datetime import datetime -from dis_snek import Intents, Snake, listen -from mongoengine import connect +from aiohttp import ClientSession +from dis_snek import Context, Intents, Snake, listen +from jarvis_core.db import connect # from jarvis import logo # noqa: F401 from jarvis import tasks, utils @@ -17,12 +21,75 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) logger.addHandler(file_handler) -intents = Intents.DEFAULT -intents.members = True +intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS restart_ctx = None +DEFAULT_GUILD = 862402786116763668 +DEFAULT_ERROR_CHANNEL = 943395824560394250 +DEFAULT_URL = "https://paste.zevs.me/documents" -jarvis = Snake(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) +ERROR_MSG = """ +Command Information: + Name: {invoked_name} + Args: +{arg_str} + +Callback: + Args: +{callback_args} + Kwargs: +{callback_kwargs} +""" + + +class Jarvis(Snake): + async def on_command_error( + self, ctx: Context, error: Exception, *args: list, **kwargs: dict + ) -> None: + """Lepton on_command_error override.""" + guild = await jarvis.get_guild(DEFAULT_GUILD) + channel = guild.get_channel(DEFAULT_ERROR_CHANNEL) + error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") + timestamp = int(datetime.now().timestamp()) + timestamp = f"" + arg_str = ( + "\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None" + ) + callback_args = "\n".join(f" - {i}" for i in args) if args else " None" + callback_kwargs = ( + "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" + ) + full_message = ERROR_MSG.format( + error_time=error_time, + invoked_name=ctx.invoked_name, + arg_str=arg_str, + callback_args=callback_args, + callback_kwargs=callback_kwargs, + ) + if len(full_message) >= 1900: + error_message = " ".join(traceback.format_exception(error)) + full_message += "Exception: |\n " + error_message + async with ClientSession() as session: + resp = await session.post(DEFAULT_URL, data=full_message) + data = await resp.read() + data = json.loads(data.decode("UTF8")) + + await channel.send( + f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." + f"\nPlease see log at https://paste.zevs.me/{data['key']}" + ) + else: + error_message = "".join(traceback.format_exception(error)) + await channel.send( + f"JARVIS encountered an error at {timestamp}:" + f"\n```yaml\n{full_message}\n```" + f"\nException:\n```py\n{error_message}\n```" + ) + await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) + return await super().on_command_error(ctx, error, *args, **kwargs) + + +jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) __version__ = "2.0.0a1" @@ -43,18 +110,7 @@ async def on_startup() -> None: def run() -> None: """Run J.A.R.V.I.S.""" - connect( - db="ctc2", - alias="ctc2", - authentication_source="admin", - **jconfig.mongo["connect"], - ) - connect( - db=jconfig.mongo["database"], - alias="main", - authentication_source="admin", - **jconfig.mongo["connect"], - ) + connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") jconfig.get_db_config() for extension in utils.get_extensions(): diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index ab26c05..f867100 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -82,8 +82,12 @@ class DevCog(Scale): data = attach.url title = attach.filename elif url.match(data): - if await get_size(data) > MAX_FILESIZE: - await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + try: + if await get_size(data) > MAX_FILESIZE: + await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + return + except Exception as e: + await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True) return title = data.split("/")[-1] diff --git a/jarvis/cogs/error.py b/jarvis/cogs/error.py deleted file mode 100644 index d245b0c..0000000 --- a/jarvis/cogs/error.py +++ /dev/null @@ -1,56 +0,0 @@ -"""J.A.R.V.I.S. error handling cog.""" -from discord.ext import commands -from discord_slash import SlashContext - -from jarvis import slash - - -class ErrorHandlerCog(commands.Cog): - """J.A.R.V.I.S. error handling cog.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.Cog.listener() - async def on_command_error(self, ctx: commands.Context, error: Exception) -> None: - """d.py on_command_error override.""" - if isinstance(error, commands.errors.MissingPermissions): - await ctx.send("I'm afraid I can't let you do that.") - elif isinstance(error, commands.errors.CommandNotFound): - return - elif isinstance(error, commands.errors.CommandOnCooldown): - await ctx.send( - "Command on cooldown. " - f"Please wait {error.retry_after:0.2f}s before trying again", - ) - else: - await ctx.send(f"Error processing command:\n```{error}```") - ctx.command.reset_cooldown(ctx) - - @commands.Cog.listener() - async def on_slash_command_error(self, ctx: SlashContext, error: Exception) -> None: - """discord_slash on_slash_command_error override.""" - if isinstance(error, commands.errors.MissingPermissions) or isinstance( - error, commands.errors.CheckFailure - ): - await ctx.send("I'm afraid I can't let you do that.", ephemeral=True) - elif isinstance(error, commands.errors.CommandNotFound): - return - elif isinstance(error, commands.errors.CommandOnCooldown): - await ctx.send( - "Command on cooldown. " - f"Please wait {error.retry_after:0.2f}s before trying again", - ephemeral=True, - ) - else: - await ctx.send( - f"Error processing command:\n```{error}```", - ephemeral=True, - ) - raise error - slash.commands[ctx.command].reset_cooldown(ctx) - - -def setup(bot: commands.Bot) -> None: - """Add ErrorHandlerCog to J.A.R.V.I.S.""" - ErrorHandlerCog(bot) From b8d1cb6cfcbef3b0bc68bde39475e630fb1805b4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 13:39:31 -0700 Subject: [PATCH 078/365] Migrate to newer versions --- jarvis/cogs/admin/kick.py | 7 ++++--- jarvis/cogs/admin/mute.py | 5 +++-- jarvis/cogs/admin/purge.py | 18 ++++++++++-------- jarvis/cogs/remindme.py | 31 ++++++++++++++++++------------- jarvis/config.py | 14 ++++++++++++++ jarvis/tasks/reminder.py | 4 ++-- jarvis/tasks/twitter.py | 6 +++--- jarvis/tasks/unban.py | 8 ++++---- jarvis/tasks/unlock.py | 8 ++++---- jarvis/tasks/unwarn.py | 4 ++-- 10 files changed, 64 insertions(+), 41 deletions(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index eb09b2a..3bb7cfb 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -8,8 +8,8 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db.models import Kick -from jarvis.db.models import Kick from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -65,10 +65,11 @@ class KickCog(Scale): embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - _ = Kick( + k = Kick( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, - ).save() + ) + await k.commit() await ctx.send(embed=embed) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 9733e6e..13aa948 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -74,14 +74,15 @@ class MuteCog(Scale): return await user.timeout(communication_disabled_until=duration, reason=reason) - _ = Mute( + m = Mute( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, active=True, - ).save() + ) + await m.commit() embed = build_embed( title="User Muted", diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 4a893fb..a5a73c6 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -37,12 +37,13 @@ class PurgeCog(Scale): async for message in ctx.channel.history(limit=amount + 1): messages.append(message) await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") - _ = Purge( + p = Purge( channel=ctx.channel.id, guild=ctx.guild.id, admin=ctx.author.id, count=amount, - ).save() + ) + await p.commit() @slash_command( name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages" @@ -78,12 +79,13 @@ class PurgeCog(Scale): await ctx.send("Autopurge already exists.", ephemeral=True) return - _ = Autopurge( + p = Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, - ).save() + ) + await p.commit() await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") @@ -98,11 +100,11 @@ class PurgeCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_MESSAGES)) async def _autopurge_remove(self, ctx: InteractionContext, channel: GuildText) -> None: - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return - autopurge.delete() + await autopurge.delete() await ctx.send(f"Autopurge removed from {channel.mention}.") @slash_command( @@ -126,12 +128,12 @@ class PurgeCog(Scale): async def _autopurge_update( self, ctx: InteractionContext, channel: GuildText, delay: int ) -> None: - autopurge = Autopurge.objects(guild=ctx.guild.id, channel=channel.id) + autopurge = await Autopurge.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autopurge: await ctx.send("Autopurge does not exist.", ephemeral=True) return autopurge.delay = delay - autopurge.save() + await autopurge.commit() await ctx.send(f"Autopurge delay updated to {delay} seconds on {channel.mention}.") diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9639485..e7a90b9 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -6,6 +6,7 @@ from typing import List from bson import ObjectId from dis_snek import InteractionContext, Snake +from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField @@ -15,8 +16,9 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) +from jarvis_core.db import q +from jarvis_core.db.models import Reminder -from jarvis.db.models import Reminder from jarvis.utils import build_embed from jarvis.utils.cachecog import CacheCog @@ -94,7 +96,7 @@ class RemindmeCog(CacheCog): await ctx.send("At least one time period is required", ephemeral=True) return - reminders = Reminder.objects(user=ctx.author.id, active=True).count() + reminders = len(await Reminder.find(q(user=ctx.author.id, active=True))) if reminders >= 5: await ctx.send( "You already have 5 (or more) active reminders. " @@ -105,7 +107,7 @@ class RemindmeCog(CacheCog): remind_at = datetime.now() + timedelta(**delta) - _ = Reminder( + r = Reminder( user=ctx.author_id, channel=ctx.channel.id, guild=ctx.guild.id, @@ -113,7 +115,9 @@ class RemindmeCog(CacheCog): remind_at=remind_at, private=private, active=True, - ).save() + ) + + await r.commit() embed = build_embed( title="Reminder Set", @@ -183,7 +187,7 @@ class RemindmeCog(CacheCog): ephemeral=True, ) return - reminders = Reminder.objects(user=ctx.author.id, active=True) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)) if not reminders: await ctx.send("You have no reminders set.", ephemeral=True) return @@ -194,7 +198,7 @@ class RemindmeCog(CacheCog): @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: - reminders = Reminder.objects(user=ctx.author.id, active=True) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)) if not reminders: await ctx.send("You have no reminders set", ephemeral=True) return @@ -230,15 +234,10 @@ class RemindmeCog(CacheCog): messages=message, timeout=60 * 5, ) - for to_delete in context.context.values: - _ = Reminder.objects(user=ctx.author.id, id=ObjectId(to_delete)).delete() - - for row in components: - for component in row.components: - component.disabled = True fields = [] - for reminder in filter(lambda x: str(x.id) in context.context.values, reminders): + for to_delete in context.context.values: + reminder = get(reminders, user=ctx.author.id, id=ObjectId(to_delete)) if reminder.private and isinstance(ctx.channel, GuildChannel): fields.append( EmbedField( @@ -255,6 +254,12 @@ class RemindmeCog(CacheCog): inline=False, ) ) + await reminder.delete() + + for row in components: + for component in row.components: + component.disabled = True + embed = build_embed( title="Deleted Reminder(s)", description="", diff --git a/jarvis/config.py b/jarvis/config.py index c2b9983..e8d65bd 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,6 +1,7 @@ """Load the config for J.A.R.V.I.S.""" import os +from jarvis_core.config import Config as CConfig from pymongo import MongoClient from yaml import load @@ -10,6 +11,19 @@ except ImportError: from yaml import Loader +class JarvisConfig(CConfig): + REQUIRED = ["token", "client_id", "mongo", "urls"] + OPTIONAL = { + "sync": False, + "log_level": "WARNING", + "scales": None, + "events": True, + "gitlab_token": None, + "max_messages": 1000, + "twitter": None, + } + + class Config(object): """Config singleton object for J.A.R.V.I.S.""" diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py index 98385ab..eba3a95 100644 --- a/jarvis/tasks/reminder.py +++ b/jarvis/tasks/reminder.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. reminder background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Reminder diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py index ff85def..20a8403 100644 --- a/jarvis/tasks/twitter.py +++ b/jarvis/tasks/twitter.py @@ -3,8 +3,8 @@ import logging from datetime import datetime, timedelta import tweepy -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Twitter @@ -41,7 +41,7 @@ async def tweets() -> None: channel_id = twitter.channel tweets = sorted(tweets, key=lambda x: x.id) if guild_id not in guild_cache: - guild_cache[guild_id] = await jarvis.jarvis.get_guild(guild_id) + guild_cache[guild_id] = await jarvis.jarvis.fetch_guild(guild_id) guild = guild_cache[twitter.guild] if channel_id not in channel_cache: channel_cache[channel_id] = await guild.fetch_channel(channel_id) diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py index 70c2b60..55a8409 100644 --- a/jarvis/tasks/unban.py +++ b/jarvis/tasks/unban.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. unban background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Ban, Unban @@ -16,8 +16,8 @@ async def _unban() -> None: bans = Ban.find(q(type="temp", active=True)) async for ban in bans: if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): - guild = await jarvis.jarvis.get_guild(ban.guild) - user = await jarvis.jarvis.get_user(ban.user) + guild = await jarvis.jarvis.fetch_guild(ban.guild) + user = await jarvis.jarvis.fetch_user(ban.user) if user: await guild.unban(user=user, reason="Ban expired") ban.update(q(active=False)) diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py index 2d8793f..22f6f15 100644 --- a/jarvis/tasks/unlock.py +++ b/jarvis/tasks/unlock.py @@ -2,8 +2,8 @@ from asyncio import to_thread from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger import jarvis from jarvis.db.models import Lock @@ -17,8 +17,8 @@ async def _unlock() -> None: if False: for lock in locks: if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow(): - guild = await jarvis.jarvis.get_guild(lock.guild) - channel = await jarvis.jarvis.get_guild(lock.channel) + guild = await jarvis.jarvis.fetch_guild(lock.guild) + channel = await guild.fetch_channel(lock.channel) if channel: roles = await guild.fetch_roles() for role in roles: diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py index c3afdac..84a07db 100644 --- a/jarvis/tasks/unwarn.py +++ b/jarvis/tasks/unwarn.py @@ -1,8 +1,8 @@ """J.A.R.V.I.S. unwarn background task handler.""" from datetime import datetime, timedelta -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Warning From fd97e82ed4ece15638c663c4d63b6b3424e78a60 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 17:04:43 -0700 Subject: [PATCH 079/365] Move tasks to jarvis-tasks --- jarvis/__init__.py | 8 +---- jarvis/tasks/__init__.py | 10 ------- jarvis/tasks/reminder.py | 45 ---------------------------- jarvis/tasks/twitter.py | 63 ---------------------------------------- jarvis/tasks/unban.py | 33 --------------------- jarvis/tasks/unlock.py | 37 ----------------------- jarvis/tasks/unwarn.py | 17 ----------- 7 files changed, 1 insertion(+), 212 deletions(-) delete mode 100644 jarvis/tasks/__init__.py delete mode 100644 jarvis/tasks/reminder.py delete mode 100644 jarvis/tasks/twitter.py delete mode 100644 jarvis/tasks/unban.py delete mode 100644 jarvis/tasks/unlock.py delete mode 100644 jarvis/tasks/unwarn.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 733cdbb..ebc6693 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -9,7 +9,7 @@ from dis_snek import Context, Intents, Snake, listen from jarvis_core.db import connect # from jarvis import logo # noqa: F401 -from jarvis import tasks, utils +from jarvis import utils from jarvis.config import get_config from jarvis.events import member, message @@ -102,12 +102,6 @@ async def on_ready() -> None: print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 -@listen() -async def on_startup() -> None: - """Lepton on_startup override.""" - tasks.init() - - def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") diff --git a/jarvis/tasks/__init__.py b/jarvis/tasks/__init__.py deleted file mode 100644 index 9825337..0000000 --- a/jarvis/tasks/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""J.A.R.V.I.S. background task handlers.""" -from jarvis.tasks import twitter, unban, unlock, unwarn - - -def init() -> None: - """Start the background task handlers.""" - unban.unban.start() - unlock.unlock.start() - unwarn.unwarn.start() - twitter.tweets.start() diff --git a/jarvis/tasks/reminder.py b/jarvis/tasks/reminder.py deleted file mode 100644 index eba3a95..0000000 --- a/jarvis/tasks/reminder.py +++ /dev/null @@ -1,45 +0,0 @@ -"""J.A.R.V.I.S. reminder background task handler.""" -from datetime import datetime, timedelta - -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger -from jarvis_core.db import q -from jarvis_core.db.models import Reminder - -import jarvis -from jarvis.utils import build_embed - - -@Task.create(trigger=IntervalTrigger(seconds=15)) -async def remind() -> None: - """J.A.R.V.I.S. reminder background task.""" - reminders = Reminder.find(q(remind_at__lte=datetime.utcnow() + timedelta(seconds=30))) - async for reminder in reminders: - if reminder.remind_at <= datetime.utcnow(): - user = await jarvis.jarvis.fetch_user(reminder.user) - if not user: - await reminder.delete() - continue - embed = build_embed( - title="You have a reminder", - description=reminder.message, - fields=[], - ) - embed.set_author( - name=user.username + "#" + user.discriminator, icon_url=user.avatar.url - ) - embed.set_thumbnail(url=user.display_avatar.url) - try: - await user.send(embed=embed) - except Exception: - guild = jarvis.jarvis.fetch_guild(reminder.guild) - channel = guild.get_channel(reminder.channel) if guild else None - if channel and not reminder.private: - await channel.send(f"{user.mention}", embed=embed) - else: - await channel.send( - f"{user.mention}, you had a private reminder set for now, " - "but I couldn't send it to you." - ) - finally: - await reminder.delete() diff --git a/jarvis/tasks/twitter.py b/jarvis/tasks/twitter.py deleted file mode 100644 index 20a8403..0000000 --- a/jarvis/tasks/twitter.py +++ /dev/null @@ -1,63 +0,0 @@ -"""J.A.R.V.I.S. twitter background task handler.""" -import logging -from datetime import datetime, timedelta - -import tweepy -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger -from jarvis_core.db import q -from jarvis_core.db.models import Twitter - -import jarvis - -logger = logging.getLogger("jarvis") - - -@Task.create(trigger=IntervalTrigger(minutes=1)) -async def tweets() -> None: - """J.A.R.V.I.S. twitter background task.""" - config = jarvis.config.get_config() - __auth = tweepy.AppAuthHandler( - config.twitter["consumer_key"], jarvis.jconfig.twitter["consumer_secret"] - ) - __api = tweepy.API(__auth) - guild_cache = {} - channel_cache = {} - twitters = Twitter.find(q(active=True)) - async for twitter in twitters: - try: - if not twitter.twitter_id or not twitter.last_sync: - user = __api.get_user(screen_name=twitter.handle) - twitter.update( - q(twitter_id=user.id, handle=user.screen_name, last_sync=datetime.now()) - ) - - if twitter.last_sync + timedelta(hours=1) <= datetime.now(): - user = __api.get_user(id=twitter.twitter_id) - twitter.update(q(handle=user.screen_name, last_sync=datetime.now())) - - if tweets := __api.user_timeline(id=twitter.twitter_id): - guild_id = twitter.guild - channel_id = twitter.channel - tweets = sorted(tweets, key=lambda x: x.id) - if guild_id not in guild_cache: - guild_cache[guild_id] = await jarvis.jarvis.fetch_guild(guild_id) - guild = guild_cache[twitter.guild] - if channel_id not in channel_cache: - channel_cache[channel_id] = await guild.fetch_channel(channel_id) - channel = channel_cache[channel_id] - for tweet in tweets: - retweet = "retweeted_status" in tweet.__dict__ - if retweet and not twitter.retweets: - continue - timestamp = int(tweet.created_at.timestamp()) - url = f"https://twitter.com/{twitter.handle}/status/{tweet.id}" - verb = "re" if retweet else "" - await channel.send( - f"`@{twitter.handle}` {verb}tweeted this at : {url}" - ) - newest = max(tweets, key=lambda x: x.id) - twitter.update(q(last_tweet=newest.id)) - await twitter.commit() - except Exception as e: - logger.error(f"Error with tweets: {e}") diff --git a/jarvis/tasks/unban.py b/jarvis/tasks/unban.py deleted file mode 100644 index 55a8409..0000000 --- a/jarvis/tasks/unban.py +++ /dev/null @@ -1,33 +0,0 @@ -"""J.A.R.V.I.S. unban background task handler.""" -from datetime import datetime, timedelta - -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger -from jarvis_core.db import q -from jarvis_core.db.models import Ban, Unban - -import jarvis - - -@Task.create(IntervalTrigger(minutes=10)) -async def _unban() -> None: - """J.A.R.V.I.S. unban background task.""" - jarvis_id = jarvis.jconfig.client_id - bans = Ban.find(q(type="temp", active=True)) - async for ban in bans: - if ban.created_at + timedelta(hours=ban.duration) < datetime.now() + timedelta(minutes=10): - guild = await jarvis.jarvis.fetch_guild(ban.guild) - user = await jarvis.jarvis.fetch_user(ban.user) - if user: - await guild.unban(user=user, reason="Ban expired") - ban.update(q(active=False)) - await ban.commit() - u = Unban( - user=user.id, - guild=guild.id, - username=user.name, - discrim=user.discriminator, - admin=jarvis_id, - reason="Ban expired", - ) - await u.commit() diff --git a/jarvis/tasks/unlock.py b/jarvis/tasks/unlock.py deleted file mode 100644 index 22f6f15..0000000 --- a/jarvis/tasks/unlock.py +++ /dev/null @@ -1,37 +0,0 @@ -"""J.A.R.V.I.S. unlock background task handler.""" -from asyncio import to_thread -from datetime import datetime, timedelta - -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger - -import jarvis -from jarvis.db.models import Lock - - -async def _unlock() -> None: - """J.A.R.V.I.S. unlock blocking task.""" - locks = Lock.objects(active=True) - # Block execution for now - # TODO: Reevaluate with admin/lock[down] - if False: - for lock in locks: - if lock.created_at + timedelta(minutes=lock.duration) < datetime.utcnow(): - guild = await jarvis.jarvis.fetch_guild(lock.guild) - channel = await guild.fetch_channel(lock.channel) - if channel: - roles = await guild.fetch_roles() - for role in roles: - overrides = channel.overwrites_for(role) - overrides.send_messages = None - await channel.set_permissions( - role, overwrite=overrides, reason="Lock expired" - ) - lock.active = False - lock.save() - - -@Task.create(IntervalTrigger(minutes=1)) -async def unlock() -> None: - """J.A.R.V.I.S. unlock background task.""" - await to_thread(_unlock) diff --git a/jarvis/tasks/unwarn.py b/jarvis/tasks/unwarn.py deleted file mode 100644 index 84a07db..0000000 --- a/jarvis/tasks/unwarn.py +++ /dev/null @@ -1,17 +0,0 @@ -"""J.A.R.V.I.S. unwarn background task handler.""" -from datetime import datetime, timedelta - -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger -from jarvis_core.db import q -from jarvis_core.db.models import Warning - - -@Task.create(IntervalTrigger(hours=1)) -async def unwarn() -> None: - """J.A.R.V.I.S. unwarn background task.""" - warns = Warning.find(q(active=True)) - async for warn in warns: - if warn.created_at + timedelta(hours=warn.duration) < datetime.now(): - warn.update(q(active=False)) - await warn.commit() From 0175fce442cf1ffd6e677a973f4fdd1288c23767 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 19 Feb 2022 20:10:05 -0700 Subject: [PATCH 080/365] Migrate twitter --- jarvis/__init__.py | 6 ++-- jarvis/cogs/twitter.py | 63 ++++++++++++++++++++++++++++-------------- poetry.lock | 4 +-- pyproject.toml | 2 +- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index ebc6693..4b743aa 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -47,8 +47,8 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" - guild = await jarvis.get_guild(DEFAULT_GUILD) - channel = guild.get_channel(DEFAULT_ERROR_CHANNEL) + guild = await jarvis.fetch_guild(DEFAULT_GUILD) + channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") timestamp = int(datetime.now().timestamp()) timestamp = f"" @@ -104,7 +104,7 @@ async def on_ready() -> None: def run() -> None: """Run J.A.R.V.I.S.""" - connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] == "jarvis") + connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") jconfig.get_db_config() for extension in utils.get_extensions(): diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 7d16b92..dc7d875 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -2,8 +2,8 @@ import asyncio import tweepy -from bson import ObjectId from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( @@ -14,7 +14,7 @@ from dis_snek.models.snek.application_commands import ( ) from dis_snek.models.snek.command import check from jarvis_core.db import q -from jarvis_core.db.models import Twitter +from jarvis_core.db.models import TwitterAccount, TwitterFollow from jarvis import jconfig from jarvis.utils.permissions import admin_or_permissions @@ -68,7 +68,7 @@ class TwitterCog(Scale): return try: - account = (await asyncio.to_thread(self.api.get_user(screen_name=handle)))[0] + account = await asyncio.to_thread(self.api.get_user, screen_name=handle) latest_tweet = (await asyncio.to_thread(self.api.user_timeline, screen_name=handle))[0] except Exception: await ctx.send( @@ -76,42 +76,54 @@ class TwitterCog(Scale): ) return - count = len([i async for i in Twitter.find(guild=ctx.guild.id)]) + count = len([i async for i in TwitterFollow.find(q(guild=ctx.guild.id))]) if count >= 12: await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) return - exists = Twitter.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) + exists = await TwitterFollow.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) if exists: await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return - t = Twitter( - handle=account.screen_name, + ta = await TwitterAccount.find_one(q(twitter_id=account.id)) + if not ta: + ta = TwitterAccount( + handle=account.screen_name, + twitter_id=account.id, + last_tweet=latest_tweet.id, + ) + await ta.commit() + + tf = TwitterFollow( twitter_id=account.id, guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, - last_tweet=latest_tweet.id, retweets=retweets, ) - await t.commit() + await tf.commit() await ctx.send(f"Now following `@{handle}` in {channel.mention}") @slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: - twitters = Twitter.objects(guild=ctx.guild.id) + t = TwitterFollow.find(q(guild=ctx.guild.id)) + twitters = [] + async for twitter in t: + twitters.append(twitter) if not twitters: await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] - handlemap = {str(x.id): x.handle for x in twitters} + handlemap = {} for twitter in twitters: - option = SelectOption(label=twitter.handle, value=str(twitter.id)) + account = await TwitterAccount.find_one(q(twitter_id=twitter.twitter_id)) + handlemap[str(twitter.twitter_id)] = account.handle + option = SelectOption(label=account.handle, value=str(twitter.twitter_id)) options.append(option) select = Select( @@ -119,7 +131,7 @@ class TwitterCog(Scale): ) components = [ActionRow(select)] - block = "\n".join(x.handle for x in twitters) + block = "\n".join(x for x in handlemap.values()) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" "Please choose accounts to unfollow", @@ -133,7 +145,8 @@ class TwitterCog(Scale): timeout=60 * 5, ) for to_delete in context.context.values: - _ = Twitter.objects(guild=ctx.guild.id, id=ObjectId(to_delete)).delete() + follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete)) + await follow.delete() for row in components: for component in row.components: component.disabled = True @@ -164,14 +177,20 @@ class TwitterCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: retweets = retweets == "Yes" - twitters = Twitter.objects(guild=ctx.guild.id) + t = TwitterFollow.find(q(guild=ctx.guild.id)) + twitters = [] + async for twitter in t: + twitters.append(twitter) if not twitters: await ctx.send("You need to follow a Twitter account first", ephemeral=True) return options = [] + handlemap = {} for twitter in twitters: - option = SelectOption(label=twitter.handle, value=str(twitter.id)) + account = await TwitterAccount.find_one(q(twitter_id=twitter.id)) + handlemap[str(twitter.twitter_id)] = account.handle + option = SelectOption(label=account.handle, value=str(twitter.twitter_id)) options.append(option) select = Select( @@ -179,7 +198,7 @@ class TwitterCog(Scale): ) components = [ActionRow(select)] - block = "\n".join(x.handle for x in twitters) + block = "\n".join(x for x in handlemap.values()) message = await ctx.send( content=f"You are following the following accounts:\n```\n{block}\n```\n\n" f"Please choose which accounts to {'un' if not retweets else ''}follow retweets from", @@ -193,11 +212,13 @@ class TwitterCog(Scale): timeout=60 * 5, ) - handlemap = {str(x.id): x.handle for x in twitters} + handlemap = {} for to_update in context.context.values: - t = await Twitter.find_one(q(guild=ctx.guild.id, id=ObjectId(to_update)))() - t.retweets = retweets - t.save() + account = await TwitterAccount.find_one(q(twitter_id=int(to_update))) + handlemap[str(twitter.twitter_id)] = account.handle + t = get(twitters, guild=ctx.guild.id, twitter_id=int(to_update)) + t.update(q(retweets=True)) + await t.commit() for row in components: for component in row.components: diff --git a/poetry.lock b/poetry.lock index af0717b..e90ef29 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.2.1" +version = "0.4.1" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "0e627eae725abb1e6f3766c5dc94bd80d0ac6702" +resolved_reference = "20f67444579d36d508def2b47ea826af9bbdd2d7" [[package]] name = "jedi" diff --git a/pyproject.toml b/pyproject.toml index a897256..98287ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jarvis" -version = "2.0.0a0" +version = "2.0.0a1" description = "J.A.R.V.I.S. admin bot" authors = ["Zevaryx "] From b12b109ad55cbf41c47c53c2cd7e25b50bae7cbd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:26:07 -0700 Subject: [PATCH 081/365] Migrate image cog --- jarvis/cogs/image.py | 104 ++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 27df52e..a9af120 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -5,13 +5,16 @@ from io import BytesIO import aiohttp import cv2 import numpy as np -from dis_snek import MessageContext, Scale, Snake, message_command +from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets - -from jarvis.utils import build_embed, convert_bytesize, unconvert_bytesize +from dis_snek.models.discord.message import Attachment +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from jarvis_core.util import build_embed, convert_bytesize, unconvert_bytesize MIN_ACCURACY = 0.80 @@ -26,39 +29,65 @@ class ImageCog(Scale): def __init__(self, bot: Snake): self.bot = bot self._session = aiohttp.ClientSession() - self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B)", re.IGNORECASE) + self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B?)", re.IGNORECASE) def __del__(self): self._session.close() - async def _resize(self, ctx: MessageContext, target: str, url: str = None) -> None: - if not target: - await ctx.send("Missing target size, i.e. 200KB.") + @slash_command(name="resize", description="Resize an image") + @slash_option( + name="target", + description="Target size, i.e. 200KB", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="attachment", + description="Image to resize", + opt_type=OptionTypes.ATTACHMENT, + required=False, + ) + @slash_option( + name="url", + description="URL to download and resize", + opt_type=OptionTypes.STRING, + required=False, + ) + async def _resize( + self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None + ) -> None: + if not attachment and not url: + await ctx.send("A URL or attachment is required", ephemeral=True) + return + + if attachment and not attachment.content_type.startswith("image"): + await ctx.send("Attachment must be an image", ephemeral=True) return tgt = self.tgt_match.match(target) if not tgt: - await ctx.send(f"Invalid target format ({target}). Expected format like 200KB") + await ctx.send( + f"Invalid target format ({target}). Expected format like 200KB", ephemeral=True + ) return tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) if tgt_size > unconvert_bytesize(8, "MB"): - await ctx.send("Target too large to send. Please make target < 8MB") + await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True) return - file = None - filename = None - if ctx.message.attachments is not None and len(ctx.message.attachments) > 0: - file = await ctx.message.attachments[0].read() - filename = ctx.message.attachments[0].filename - elif url is not None: - async with self._session.get(url) as resp: - if resp.status == 200: - file = await resp.read() - filename = url.split("/")[-1] - else: - ctx.send("Missing file as either attachment or URL.") - size = len(file) + if attachment: + url = attachment.url + filename = attachment.filename + else: + filename = url.split("/")[-1] + + data = None + async with self._session.get(url) as resp: + if resp.status == 200: + data = await resp.read() + + size = len(data) if size <= tgt_size: await ctx.send("Image already meets target.") return @@ -67,43 +96,38 @@ class ImageCog(Scale): accuracy = 0.0 - while len(file) > tgt_size or (len(file) <= tgt_size and accuracy < MIN_ACCURACY): - old_file = file + while len(data) > tgt_size or (len(data) <= tgt_size and accuracy < MIN_ACCURACY): + old_file = data - buffer = np.frombuffer(file, dtype=np.uint8) + buffer = np.frombuffer(data, dtype=np.uint8) img = cv2.imdecode(buffer, flags=-1) width = int(img.shape[1] * ratio) height = int(img.shape[0] * ratio) new_img = cv2.resize(img, (width, height)) - file = cv2.imencode(".png", new_img)[1].tobytes() - accuracy = (len(file) / tgt_size) * 100 + data = cv2.imencode(".png", new_img)[1].tobytes() + accuracy = (len(data) / tgt_size) * 100 if accuracy <= 0.50: - file = old_file + data = old_file ratio += 0.1 else: - ratio = max(tgt_size / len(file) - 0.02, 0.65) + ratio = max(tgt_size / len(data) - 0.02, 0.65) - bufio = BytesIO(file) - accuracy = (len(file) / tgt_size) * 100 + bufio = BytesIO(data) + accuracy = (len(data) / tgt_size) * 100 fields = [ EmbedField("Original Size", convert_bytesize(size), False), - EmbedField("New Size", convert_bytesize(len(file)), False), + EmbedField("New Size", convert_bytesize(len(data)), False), EmbedField("Accuracy", f"{accuracy:.02f}%", False), ] embed = build_embed(title=filename, description="", fields=fields) embed.set_image(url="attachment://resized.png") await ctx.send( embed=embed, - file=File(file=bufio, filename="resized.png"), + file=File(file=bufio, file_name="resized.png"), ) - @message_command(name="resize") - @cooldown(bucket=Buckets.USER, rate=1, interval=60) - async def _resize_pref(self, ctx: MessageContext, target: str, url: str = None) -> None: - await self._resize(ctx, target, url) - def setup(bot: Snake) -> None: """Add ImageCog to J.A.R.V.I.S.""" From 6349321e5cc54c2235ddac420f60bcdce5fe40de Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:26:41 -0700 Subject: [PATCH 082/365] Update remindme to use modals --- jarvis/cogs/remindme.py | 63 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index e7a90b9..ab4aae6 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -5,11 +5,12 @@ from datetime import datetime, timedelta from typing import List from bson import ObjectId -from dis_snek import InteractionContext, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -20,35 +21,19 @@ from jarvis_core.db import q from jarvis_core.db.models import Reminder from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog -valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") -time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?") +valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>:,\u0080-\U000E0FFF]*") +time_pattern = re.compile(r"(\d+\.?\d?[s|m|h|d|w]{1})\s?", flags=re.IGNORECASE) invites = re.compile( r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 flags=re.IGNORECASE, ) -class RemindmeCog(CacheCog): +class RemindmeCog(Scale): """J.A.R.V.I.S. Remind Me Cog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command(name="remindme", description="Set a reminder") - @slash_option( - name="message", - description="What to remind you of?", - opt_type=OptionTypes.STRING, - required=True, - ) - @slash_option( - name="delay", - description="How long? (i.e. 1w 3d 7h 5m 20s)", - opt_type=OptionTypes.STRING, - required=False, - ) @slash_option( name="private", description="Send as DM?", @@ -62,13 +47,35 @@ class RemindmeCog(CacheCog): async def _remindme( self, ctx: InteractionContext, - message: str, - delay: str, private: str = "n", ) -> None: private = private == "y" - if len(message) > 100: - await ctx.send("Reminder cannot be > 100 characters.", ephemeral=True) + modal = Modal( + title="Set your reminder!", + components=[ + InputText( + label="What to remind you?", + placeholder="Reminder", + style=TextStyles.PARAGRAPH, + custom_id="message", + ), + InputText( + label="When to remind you?", + placeholder="1h 30m", + style=TextStyles.SHORT, + custom_id="delay", + ), + ], + ) + await ctx.send_modal(modal) + try: + response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + message = response.responses.get("message") + delay = response.responses.get("delay") + except asyncio.TimeoutError: + return + if len(message) > 500: + await ctx.send("Reminder cannot be > 500 characters.", ephemeral=True) return elif invites.search(message): await ctx.send( @@ -83,7 +90,7 @@ class RemindmeCog(CacheCog): units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"} delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0} - if times := time_pattern.findall(delay, flags=re.I): + if times := time_pattern.findall(delay): for t in times: delta[units[t[-1]]] += float(t[:-1]) else: @@ -96,7 +103,7 @@ class RemindmeCog(CacheCog): await ctx.send("At least one time period is required", ephemeral=True) return - reminders = len(await Reminder.find(q(user=ctx.author.id, active=True))) + reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))]) if reminders >= 5: await ctx.send( "You already have 5 (or more) active reminders. " @@ -108,7 +115,7 @@ class RemindmeCog(CacheCog): remind_at = datetime.now() + timedelta(**delta) r = Reminder( - user=ctx.author_id, + user=ctx.author.id, channel=ctx.channel.id, guild=ctx.guild.id, message=message, @@ -138,7 +145,7 @@ class RemindmeCog(CacheCog): ) embed.set_thumbnail(url=ctx.author.display_avatar.url) - await ctx.send(embed=embed, ephemeral=private) + await response.send(embed=embed, ephemeral=private) async def get_reminders_embed( self, ctx: InteractionContext, reminders: List[Reminder] From 33440f6150a94babc5c5393542c7a2907c96afae Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:27:55 -0700 Subject: [PATCH 083/365] Move custom Jarvis client class to own file --- jarvis/__init__.py | 84 ++-------------------------------------------- jarvis/client.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 82 deletions(-) create mode 100644 jarvis/client.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 4b743aa..b7c6f1a 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,15 +1,12 @@ """Main J.A.R.V.I.S. package.""" -import json import logging -import traceback -from datetime import datetime -from aiohttp import ClientSession -from dis_snek import Context, Intents, Snake, listen +from dis_snek import Intents from jarvis_core.db import connect # from jarvis import logo # noqa: F401 from jarvis import utils +from jarvis.client import Jarvis from jarvis.config import get_config from jarvis.events import member, message @@ -24,84 +21,12 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS restart_ctx = None -DEFAULT_GUILD = 862402786116763668 -DEFAULT_ERROR_CHANNEL = 943395824560394250 -DEFAULT_URL = "https://paste.zevs.me/documents" - -ERROR_MSG = """ -Command Information: - Name: {invoked_name} - Args: -{arg_str} - -Callback: - Args: -{callback_args} - Kwargs: -{callback_kwargs} -""" - - -class Jarvis(Snake): - async def on_command_error( - self, ctx: Context, error: Exception, *args: list, **kwargs: dict - ) -> None: - """Lepton on_command_error override.""" - guild = await jarvis.fetch_guild(DEFAULT_GUILD) - channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) - error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") - timestamp = int(datetime.now().timestamp()) - timestamp = f"" - arg_str = ( - "\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None" - ) - callback_args = "\n".join(f" - {i}" for i in args) if args else " None" - callback_kwargs = ( - "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" - ) - full_message = ERROR_MSG.format( - error_time=error_time, - invoked_name=ctx.invoked_name, - arg_str=arg_str, - callback_args=callback_args, - callback_kwargs=callback_kwargs, - ) - if len(full_message) >= 1900: - error_message = " ".join(traceback.format_exception(error)) - full_message += "Exception: |\n " + error_message - async with ClientSession() as session: - resp = await session.post(DEFAULT_URL, data=full_message) - data = await resp.read() - data = json.loads(data.decode("UTF8")) - - await channel.send( - f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." - f"\nPlease see log at https://paste.zevs.me/{data['key']}" - ) - else: - error_message = "".join(traceback.format_exception(error)) - await channel.send( - f"JARVIS encountered an error at {timestamp}:" - f"\n```yaml\n{full_message}\n```" - f"\nException:\n```py\n{error_message}\n```" - ) - await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) - return await super().on_command_error(ctx, error, *args, **kwargs) - jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) __version__ = "2.0.0a1" -@listen() -async def on_ready() -> None: - """Lepton on_ready override.""" - global restart_ctx - print(" Logged in as {0.user}".format(jarvis)) # noqa: T001 - print(" Connected to {} guild(s)".format(len(jarvis.guilds))) # noqa: T001 - - def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") @@ -110,11 +35,6 @@ def run() -> None: for extension in utils.get_extensions(): jarvis.load_extension(extension) - print( # noqa: T001 - " https://discord.com/api/oauth2/authorize?client_id=" - "{}&permissions=8&scope=bot%20applications.commands".format(jconfig.client_id) - ) - jarvis.max_messages = jconfig.max_messages # Add event listeners diff --git a/jarvis/client.py b/jarvis/client.py new file mode 100644 index 0000000..6d5792f --- /dev/null +++ b/jarvis/client.py @@ -0,0 +1,81 @@ +"""Custom JARVIS client.""" +import json +import traceback +from datetime import datetime + +from aiohttp import ClientSession +from dis_snek import Context, Snake, listen + +DEFAULT_GUILD = 862402786116763668 +DEFAULT_ERROR_CHANNEL = 943395824560394250 +DEFAULT_URL = "https://paste.zevs.me/documents" + +ERROR_MSG = """ +Command Information: + Name: {invoked_name} + Args: +{arg_str} + +Callback: + Args: +{callback_args} + Kwargs: +{callback_kwargs} +""" + + +class Jarvis(Snake): + @listen() + async def on_ready(self) -> None: + """Lepton on_ready override.""" + print("Logged in as {}".format(self.user)) # noqa: T001 + print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 + print( # noqa: T001 + "https://discord.com/api/oauth2/authorize?client_id=" + "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) + ) + + async def on_command_error( + self, ctx: Context, error: Exception, *args: list, **kwargs: dict + ) -> None: + """Lepton on_command_error override.""" + guild = await self.fetch_guild(DEFAULT_GUILD) + channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) + error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") + timestamp = int(datetime.now().timestamp()) + timestamp = f"" + arg_str = ( + "\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None" + ) + callback_args = "\n".join(f" - {i}" for i in args) if args else " None" + callback_kwargs = ( + "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" + ) + full_message = ERROR_MSG.format( + error_time=error_time, + invoked_name=ctx.invoked_name, + arg_str=arg_str, + callback_args=callback_args, + callback_kwargs=callback_kwargs, + ) + if len(full_message) >= 1900: + error_message = " ".join(traceback.format_exception(error)) + full_message += "Exception: |\n " + error_message + async with ClientSession() as session: + resp = await session.post(DEFAULT_URL, data=full_message) + data = await resp.read() + data = json.loads(data.decode("UTF8")) + + await channel.send( + f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." + f"\nPlease see log at https://paste.zevs.me/{data['key']}" + ) + else: + error_message = "".join(traceback.format_exception(error)) + await channel.send( + f"JARVIS encountered an error at {timestamp}:" + f"\n```yaml\n{full_message}\n```" + f"\nException:\n```py\n{error_message}\n```" + ) + await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) + return await super().on_command_error(ctx, error, *args, **kwargs) From a0fdd944325422ed6547a35d47f1885d15c7fea5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:30:25 -0700 Subject: [PATCH 084/365] Add max length to remindme message --- jarvis/cogs/remindme.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index ab4aae6..e53a532 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -58,6 +58,7 @@ class RemindmeCog(Scale): placeholder="Reminder", style=TextStyles.PARAGRAPH, custom_id="message", + max_length=500, ), InputText( label="When to remind you?", From 40fa82df6637940ba5c8ce5d1149f9153ff0a280 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 01:48:54 -0700 Subject: [PATCH 085/365] Remove owner cog --- jarvis/cogs/owner.py | 46 -------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 jarvis/cogs/owner.py diff --git a/jarvis/cogs/owner.py b/jarvis/cogs/owner.py deleted file mode 100644 index 094faf4..0000000 --- a/jarvis/cogs/owner.py +++ /dev/null @@ -1,46 +0,0 @@ -"""J.A.R.V.I.S. Owner Cog.""" -from dis_snek import MessageContext, Scale, Snake, message_command -from dis_snek.models.discord.user import User -from dis_snek.models.snek.checks import is_owner -from dis_snek.models.snek.command import check - -from jarvis import jconfig - - -class OwnerCog(Scale): - """ - J.A.R.V.I.S. management cog. - - Used by admins to control core J.A.R.V.I.S. systems - """ - - def __init__(self, bot: Snake): - self.bot = bot - # self.admins = await Config.find_one(q(key="admins")) - - @message_command(name="addadmin") - @check(is_owner()) - async def _add(self, ctx: MessageContext, user: User) -> None: - if user.id in self.admins.value: - await ctx.send(f"{user.mention} is already an admin.") - return - self.admins.value.append(user.id) - self.admins.save() - jconfig.reload() - await ctx.send(f"{user.mention} is now an admin. Use this power carefully.") - - @message_command(name="deladmin") - @is_owner() - async def _remove(self, ctx: MessageContext, user: User) -> None: - if user.id not in self.admins.value: - await ctx.send(f"{user.mention} is not an admin.") - return - self.admins.value.remove(user.id) - self.admins.save() - jconfig.reload() - await ctx.send(f"{user.mention} is no longer an admin.") - - -def setup(bot: Snake) -> None: - """Add OwnerCog to J.A.R.V.I.S.""" - OwnerCog(bot) From 824548b04dee0021e89002926882ea4d8c32ca7d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 13:04:18 -0700 Subject: [PATCH 086/365] Add on_command listener --- jarvis/__init__.py | 2 +- jarvis/client.py | 39 ++++++++++++++++++++++++++++++++++++++- poetry.lock | 4 ++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index b7c6f1a..45d3232 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -18,7 +18,7 @@ file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) logger.addHandler(file_handler) -intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS +intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None diff --git a/jarvis/client.py b/jarvis/client.py index 6d5792f..253abf0 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -4,7 +4,14 @@ import traceback from datetime import datetime from aiohttp import ClientSession -from dis_snek import Context, Snake, listen +from dis_snek import Snake, listen +from dis_snek.models.discord.channel import DMChannel +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.snek.context import Context, InteractionContext +from jarvis_core.db import q +from jarvis_core.db.models import Setting +from jarvis_core.util import build_embed +from jarvis_core.util.ansi import RESET, Fore, Format, fmt DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 @@ -23,6 +30,10 @@ Callback: {callback_kwargs} """ +KEY_FMT = fmt(Fore.GRAY) +VAL_FMT = fmt(Fore.WHITE) +CMD_FMT = fmt(Fore.GREEN, Format.BOLD) + class Jarvis(Snake): @listen() @@ -35,6 +46,32 @@ class Jarvis(Snake): "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) + async def on_command(self, ctx: InteractionContext) -> None: + """Lepton on_command override.""" + if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: + modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) + if modlog: + channel = await ctx.guild.fetch_channel(modlog.value) + args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items()) + fields = [ + EmbedField( + name="Command", + value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", + inline=False, + ), + ] + embed = build_embed( + title="Command Invoked", + description=f"{ctx.author.mention} invoked a command", + fields=fields, + color="#fc9e3f", + ) + embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) + await channel.send(embed=embed) + async def on_command_error( self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: diff --git a/poetry.lock b/poetry.lock index e90ef29..830b51d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.4.1" +version = "0.5.0" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "20f67444579d36d508def2b47ea826af9bbdd2d7" +resolved_reference = "f392757bb773cfeb9b3f14d581a599d13ee6e891" [[package]] name = "jedi" From a72ede0644cb71d4d19df9bdb501d7987e1d61f8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 13:04:47 -0700 Subject: [PATCH 087/365] Remove jokes --- jarvis/cogs/jokes.py | 122 ------------------------------------------- 1 file changed, 122 deletions(-) delete mode 100644 jarvis/cogs/jokes.py diff --git a/jarvis/cogs/jokes.py b/jarvis/cogs/jokes.py deleted file mode 100644 index 7697d37..0000000 --- a/jarvis/cogs/jokes.py +++ /dev/null @@ -1,122 +0,0 @@ -"""J.A.R.V.I.S. Jokes module.""" -import html -import re -import traceback -from datetime import datetime -from random import randint - -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.snek.application_commands import ( - OptionTypes, - slash_command, - slash_option, -) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets -from jarvis_core.db import q -from jarvis_core.db.models import Joke - -from jarvis.utils import build_embed - - -class JokeCog(Scale): - """ - Joke library for J.A.R.V.I.S. - - May adapt over time to create jokes using machine learning - """ - - def __init__(self, bot: Snake): - self.bot = bot - - # TODO: Make this a command group with subcommands - @slash_command( - name="joke", - description="Hear a joke", - ) - @slash_option(name="id", description="Joke ID", required=False, opt_type=OptionTypes.INTEGER) - @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=10) - async def _joke(self, ctx: InteractionContext, id: str = None) -> None: - """Get a joke from the database.""" - try: - if randint(1, 100_000) == 5779 and id is None: # noqa: S311 - await ctx.send(f"<@{ctx.message.author.id}>") - return - # TODO: Add this as a parameter that can be passed in - threshold = 500 # Minimum score - result = None - if id: - result = await Joke.find_one(q(rid=id)) - else: - pipeline = [ - {"$match": {"score": {"$gt": threshold}}}, - {"$sample": {"size": 1}}, - ] - result = Joke.objects().aggregate(pipeline).next() - while result["body"] in ["[removed]", "[deleted]"]: - result = Joke.objects().aggregate(pipeline).next() - - if result is None: - await ctx.send("Humor module failed. Please try again later.", ephemeral=True) - return - emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["body"]) - for match in emotes: - result["body"] = result["body"].replace(match, html.unescape(match)) - emotes = re.findall(r"(&#x[a-fA-F0-9]*;)", result["title"]) - for match in emotes: - result["title"] = result["title"].replace(match, html.unescape(match)) - body_chunks = [] - - body = "" - for word in result["body"].split(" "): - if len(body) + 1 + len(word) > 1024: - body_chunks.append(EmbedField("​", body, False)) - body = "" - if word == "\n" and body == "": - continue - elif word == "\n": - body += word - else: - body += " " + word - - desc = "" - title = result["title"] - if len(title) > 256: - new_title = "" - limit = False - for word in title.split(" "): - if len(new_title) + len(word) + 1 > 253 and not limit: - new_title += "..." - desc = "..." - limit = True - if not limit: - new_title += word + " " - else: - desc += word + " " - - body_chunks.append(EmbedField("​", body, False)) - - fields = body_chunks - fields.append(EmbedField("Score", result["score"])) - # Field( - # "Created At", - # str(datetime.fromtimestamp(result["created_utc"])), - # ), - fields.append(EmbedField("ID", result["rid"])) - embed = build_embed( - title=title, - description=desc, - fields=fields, - url=f"https://reddit.com/r/jokes/comments/{result['rid']}", - timestamp=datetime.fromtimestamp(result["created_utc"]), - ) - await ctx.send(embed=embed) - except Exception: - await ctx.send("Encountered error:\n```\n" + traceback.format_exc() + "\n```") - # await ctx.send(f"**{result['title']}**\n\n{result['body']}") - - -def setup(bot: Snake) -> None: - """Add JokeCog to J.A.R.V.I.S.""" - JokeCog(bot) From cbf6587dbfe9143a3fd3883eb3bbb804256a95f1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 14:05:47 -0700 Subject: [PATCH 088/365] Move command to client --- jarvis/cogs/modlog/command.py | 38 ----------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 jarvis/cogs/modlog/command.py diff --git a/jarvis/cogs/modlog/command.py b/jarvis/cogs/modlog/command.py deleted file mode 100644 index f846f4c..0000000 --- a/jarvis/cogs/modlog/command.py +++ /dev/null @@ -1,38 +0,0 @@ -"""J.A.R.V.I.S. ModlogCommandCog.""" -from dis_snek import InteractionContext, Snake, listen -from dis_snek.models.discord.channel import DMChannel -from dis_snek.models.discord.embed import EmbedField - -from jarvis.db.models import Setting -from jarvis.utils import build_embed - - -class ModlogCommandCog(object): - """J.A.R.V.I.S. ModlogCommandCog.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_command) - - @listen() - async def on_command(self, ctx: InteractionContext) -> None: - """Process on_slash_command events.""" - if not isinstance(ctx.channel, DMChannel) and ctx.name not in ["pw"]: - modlog = Setting.objects(guild=ctx.guild.id, setting="modlog").first() - if modlog: - channel = await ctx.guild.get_channel(modlog.value) - args = " ".join(f"{k}:v" for k, v in ctx.kwargs.items()) - fields = [ - EmbedField(name="Command", value=f"{ctx.invoked_name} {args}", inline=False), - ] - embed = build_embed( - title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", - fields=fields, - color="#fc9e3f", - ) - embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) - embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" - ) - await channel.send(embed=embed) From 90506c136efb2e1d816b26392ba6f51e0382cfce Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 14:07:09 -0700 Subject: [PATCH 089/365] Update modlog, member needs full re-work --- jarvis/cogs/modlog/__init__.py | 8 +- jarvis/cogs/modlog/member.py | 333 +-------------------------------- jarvis/cogs/modlog/message.py | 54 +++--- jarvis/cogs/modlog/utils.py | 37 ++-- 4 files changed, 46 insertions(+), 386 deletions(-) diff --git a/jarvis/cogs/modlog/__init__.py b/jarvis/cogs/modlog/__init__.py index 63c09e2..f59f2c3 100644 --- a/jarvis/cogs/modlog/__init__.py +++ b/jarvis/cogs/modlog/__init__.py @@ -1,11 +1,11 @@ """J.A.R.V.I.S. Modlog Cogs.""" -from discord.ext.commands import Bot +from dis_snek import Snake -from jarvis.cogs.modlog import command, member, message +from jarvis.cogs.modlog import command, message -def setup(bot: Bot) -> None: +def setup(bot: Snake) -> None: """Add modlog cogs to J.A.R.V.I.S.""" command.ModlogCommandCog(bot) - member.ModlogMemberCog(bot) + # member.ModlogMemberCog(bot) message.ModlogMessageCog(bot) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py index 99302d7..b00514e 100644 --- a/jarvis/cogs/modlog/member.py +++ b/jarvis/cogs/modlog/member.py @@ -1,332 +1 @@ -"""J.A.R.V.I.S. ModlogMemberCog.""" -import asyncio -from datetime import datetime, timedelta - -import discord -from discord.ext import commands -from discord.utils import find - -from jarvis.cogs.modlog.utils import get_latest_log, modlog_embed -from jarvis.config import get_config -from jarvis.db.models import Ban, Kick, Mute, Setting, Unban -from jarvis.utils import build_embed -from jarvis.utils.field import Field - - -class ModlogMemberCog(commands.Cog): - """J.A.R.V.I.S. ModlogMemberCog.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - self.cache = [] - - @commands.Cog.listener() - async def on_member_ban(self, guild: discord.Guild, user: discord.User) -> None: - """Process on_member_ban events.""" - modlog = Setting.objects(guild=guild.id, setting="modlog").first() - if modlog: - channel = guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await guild.audit_logs( - limit=50, - action=discord.AuditLogAction.ban, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - ban = ( - Ban.objects( - guild=guild.id, - user=user.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if ban: - admin = guild.get_member(ban.admin) - embed = modlog_embed( - user, - admin, - log, - "User banned", - f"{user.mention} was banned from {guild.name}", - ) - - await channel.send(embed=embed) - - @commands.Cog.listener() - async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None: - """Process on_member_unban events.""" - modlog = Setting.objects(guild=guild.id, setting="modlog").first() - if modlog: - channel = guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await guild.audit_logs( - limit=50, - action=discord.AuditLogAction.unban, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - unban = ( - Unban.objects( - guild=guild.id, - user=user.id, - ) - .order_by("-created_at") - .first() - ) - admin = guild.get_member(unban.admin) - embed = modlog_embed( - user, - admin, - log, - "User unbanned", - f"{user.mention} was unbanned from {guild.name}", - ) - - await channel.send(embed=embed) - - @commands.Cog.listener() - async def on_member_remove(self, user: discord.Member) -> None: - """Process on_member_remove events.""" - modlog = Setting.objects(guild=user.guild.id, setting="modlog").first() - if modlog: - channel = user.guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await user.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.kick, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - count = 0 - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - while not log: - if count == 30: - break - await asyncio.sleep(0.5) - log: discord.AuditLogEntry = get_latest_log(auditlog, user) - count += 1 - if not log: - return - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - kick = ( - Kick.objects( - guild=user.guild.id, - user=user.id, - ) - .order_by("-created_at") - .first() - ) - if kick: - admin = user.guild.get_member(kick.admin) - embed = modlog_embed( - user, - admin, - log, - "User Kicked", - f"{user.mention} was kicked from {user.guild.name}", - ) - - await channel.send(embed=embed) - - async def process_mute(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process mute event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - mute = ( - Mute.objects( - guild=before.guild.id, - user=before.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if mute: - admin = before.guild.get_member(mute.admin) - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Muted", - desc=f"{before.mention} was muted", - ) - - async def process_unmute(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process unmute event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - if admin.id == get_config().client_id: - await asyncio.sleep(3) - mute = ( - Mute.objects( - guild=before.guild.id, - user=before.id, - active=True, - ) - .order_by("-created_at") - .first() - ) - if mute: - admin = before.guild.get_member(mute.admin) - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Muted", - desc=f"{before.mention} was muted", - ) - - async def process_verify(self, before: discord.Member, after: discord.Member) -> discord.Embed: - """Process verification event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - return modlog_embed( - member=before, - admin=admin, - log=log, - title="User Verified", - desc=f"{before.mention} was verified", - ) - - async def process_rolechange( - self, before: discord.Member, after: discord.Member - ) -> discord.Embed: - """Process rolechange event.""" - await asyncio.sleep(0.5) # Need to wait for audit log - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_role_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - admin: discord.User = log.user - role = None - title = "User Given Role" - verb = "was given" - if len(before.roles) > len(after.roles): - title = "User Forfeited Role" - verb = "forfeited" - role = find(lambda x: x not in after.roles, before.roles) - elif len(before.roles) < len(after.roles): - role = find(lambda x: x not in before.roles, after.roles) - role_text = role.mention if role else "||`[redacted]`||" - return modlog_embed( - member=before, - admin=admin, - log=log, - title=title, - desc=f"{before.mention} {verb} role {role_text}", - ) - - @commands.Cog.listener() - async def on_member_update(self, before: discord.Member, after: discord.Member) -> None: - """Process on_member_update events. - - Caches events due to double-send bug - """ - h = hash(hash(before) * hash(after)) - if h not in self.cache: - self.cache.append(h) - else: - return - modlog = Setting.objects(guild=before.guild.id, setting="modlog").first() - if modlog: - channel = after.guild.get_channel(modlog.value) - await asyncio.sleep(0.5) # Need to wait for audit log - embed = None - mute = Setting.objects(guild=before.guild.id, setting="mute").first() - verified = Setting.objects(guild=before.guild.id, setting="verified").first() - mute_role = None - verified_role = None - if mute: - mute_role = before.guild.get_role(mute.value) - if verified: - verified_role = before.guild.get_role(verified.value) - if mute and mute_role in after.roles and mute_role not in before.roles: - embed = await self.process_mute(before, after) - elif mute and mute_role in before.roles and mute_role not in after.roles: - embed = await self.process_unmute(before, after) - elif verified and verified_role not in before.roles and verified_role in after.roles: - embed = await self.process_verify(before, after) - elif before.nick != after.nick: - auditlog = await before.guild.audit_logs( - limit=50, - action=discord.AuditLogAction.member_update, - after=datetime.utcnow() - timedelta(seconds=15), - oldest_first=False, - ).flatten() - log: discord.AuditLogEntry = get_latest_log(auditlog, before) - bname = before.nick if before.nick else before.name - aname = after.nick if after.nick else after.name - fields = [ - Field( - name="Before", - value=f"{bname} ({before.name}#{before.discriminator})", - ), - Field( - name="After", - value=f"{aname} ({after.name}#{after.discriminator})", - ), - ] - if log.user.id != before.id: - fields.append( - Field( - name="Moderator", - value=f"{log.user.mention} ({log.user.name}#{log.user.discriminator})", - ) - ) - if log.reason: - fields.append( - Field(name="Reason", value=log.reason, inline=False), - ) - embed = build_embed( - title="User Nick Changed", - description=f"{after.mention} changed their nickname", - color="#fc9e3f", - fields=fields, - timestamp=log.created_at, - ) - embed.set_author(name=f"{after.name}", icon_url=after.display_avatar.url) - embed.set_footer(text=f"{after.name}#{after.discriminator} | {after.id}") - elif len(before.roles) != len(after.roles): - # TODO: User got a new role - embed = await self.process_rolechange(before, after) - if embed: - await channel.send(embed=embed) - self.cache.remove(h) +"""JARVIS member modlog processing.""" diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py index a00bb69..848a929 100644 --- a/jarvis/cogs/modlog/message.py +++ b/jarvis/cogs/modlog/message.py @@ -1,34 +1,37 @@ """J.A.R.V.I.S. ModlogMessageCog.""" -import discord -from discord.ext import commands +from dis_snek import Scale, Snake, listen +from dis_snek.api.events.discord import MessageDelete, MessageUpdate +from dis_snek.models.discord.embed import EmbedField +from jarvis_core.db import q +from jarvis_core.db.models import Setting -from jarvis.db.models import Setting from jarvis.utils import build_embed -from jarvis.utils.field import Field -class ModlogMessageCog(commands.Cog): +class ModlogMessageCog(Scale): """J.A.R.V.I.S. ModlogMessageCog.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Snake): self.bot = bot - @commands.Cog.listener() - async def on_message_edit(self, before: discord.Message, after: discord.Message) -> None: + @listen() + async def on_message_edit(self, event: MessageUpdate) -> None: """Process on_message_edit events.""" + before = event.before + after = event.after if not before.author.bot: - modlog = Setting.objects(guild=after.guild.id, setting="modlog").first() + modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) if modlog: if before.content == after.content or before.content is None: return channel = before.guild.get_channel(modlog.value) fields = [ - Field( + EmbedField( "Original Message", before.content if before.content else "N/A", False, ), - Field( + EmbedField( "New Message", after.content if after.content else "N/A", False, @@ -39,40 +42,41 @@ class ModlogMessageCog(commands.Cog): description=f"{before.author.mention} edited a message", fields=fields, color="#fc9e3f", - timestamp=after.edited_at, + timestamp=after.edited_timestamp, url=after.jump_url, ) embed.set_author( - name=before.author.name, + name=before.author.username, icon_url=before.author.display_avatar.url, url=after.jump_url, ) embed.set_footer( - text=f"{before.author.name}#{before.author.discriminator} | {before.author.id}" + text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" ) await channel.send(embed=embed) - @commands.Cog.listener() - async def on_message_delete(self, message: discord.Message) -> None: + @listen() + async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" - modlog = Setting.objects(guild=message.guild.id, setting="modlog").first() + message = event.message + modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) if modlog: - fields = [Field("Original Message", message.content or "N/A", False)] + fields = [EmbedField("Original Message", message.content or "N/A", False)] if message.attachments: value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) fields.append( - Field( + EmbedField( name="Attachments", value=value, inline=False, ) ) - if message.stickers: - value = "\n".join([f"[{x.name}]({x.image_url})" for x in message.stickers]) + if message.sticker_items: + value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) fields.append( - Field( + EmbedField( name="Stickers", value=value, inline=False, @@ -82,7 +86,7 @@ class ModlogMessageCog(commands.Cog): if message.embeds: value = str(len(message.embeds)) + " embeds" fields.append( - Field( + EmbedField( name="Embeds", value=value, inline=False, @@ -98,11 +102,11 @@ class ModlogMessageCog(commands.Cog): ) embed.set_author( - name=message.author.name, + name=message.author.username, icon_url=message.author.display_avatar.url, url=message.jump_url, ) embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" ) await channel.send(embed=embed) diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py index 28a63fb..72b5cbe 100644 --- a/jarvis/cogs/modlog/utils.py +++ b/jarvis/cogs/modlog/utils.py @@ -1,31 +1,27 @@ """J.A.R.V.I.S. Modlog Cog Utilities.""" -from datetime import datetime, timedelta -from typing import List - -import discord -from discord import AuditLogEntry, Member -from discord.utils import find +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.guild import AuditLogEntry +from dis_snek.models.discord.user import Member from jarvis.utils import build_embed -from jarvis.utils.field import Field def modlog_embed( - member: discord.Member, - admin: discord.Member, - log: discord.AuditLogEntry, + member: Member, + admin: Member, + log: AuditLogEntry, title: str, desc: str, -) -> discord.Embed: +) -> Embed: """Get modlog embed.""" fields = [ - Field( + EmbedField( name="Moderator", - value=f"{admin.mention} ({admin.name}#{admin.discriminator})", + value=f"{admin.mention} ({admin.username}#{admin.discriminator})", ), ] if log.reason: - fields.append(Field(name="Reason", value=log.reason, inline=False)) + fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) embed = build_embed( title=title, description=desc, @@ -33,15 +29,6 @@ def modlog_embed( fields=fields, timestamp=log.created_at, ) - embed.set_author(name=f"{member.name}", icon_url=member.display_avatar.url) - embed.set_footer(text=f"{member.name}#{member.discriminator} | {member.id}") + embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) + embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") return embed - - -def get_latest_log(auditlog: List[AuditLogEntry], target: Member) -> AuditLogEntry: - """Filter AuditLog to get latest entry.""" - before = datetime.utcnow() - timedelta(seconds=10) - return find( - lambda x: x.target.id == target.id and x.created_at > before, - auditlog, - ) From 2a84d50c6538ef8ef39551ac7ea87b045211d356 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 21 Feb 2022 15:11:22 -0700 Subject: [PATCH 090/365] Disable lock and lockdown until dis-snek permissions work --- jarvis/cogs/admin/lock.py | 188 ++++++++++++++--------------- jarvis/cogs/admin/mute.py | 242 +++++++++++++++++++------------------- 2 files changed, 215 insertions(+), 215 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 6fe34b7..e17cf58 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. LockCog.""" -from dis_snek import Scale - -# TODO: Uncomment 99% of code once implementation is figured out +# from dis_snek import Scale +# +# # TODO: Uncomment 99% of code once implementation is figured out # from contextlib import suppress # from typing import Union # @@ -20,94 +20,94 @@ from dis_snek import Scale # # from jarvis.db.models import Lock # from jarvis.utils.permissions import admin_or_permissions - - -class LockCog(Scale): - """J.A.R.V.I.S. LockCog.""" - - # @slash_command(name="lock", description="Lock a channel") - # @slash_option(name="reason", - # description="Lock Reason", - # opt_type=3, - # required=True,) - # @slash_option(name="duration", - # description="Lock duration in minutes (default 10)", - # opt_type=4, - # required=False,) - # @slash_option(name="channel", - # description="Channel to lock", - # opt_type=7, - # required=False,) - # @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) - # async def _lock( - # self, - # ctx: InteractionContext, - # reason: str, - # duration: int = 10, - # channel: Union[GuildText, GuildVoice] = None, - # ) -> None: - # await ctx.defer(ephemeral=True) - # if duration <= 0: - # await ctx.send("Duration must be > 0", ephemeral=True) - # return - # - # elif duration > 60 * 12: - # await ctx.send("Duration must be <= 12 hours", ephemeral=True) - # return - # - # if len(reason) > 100: - # await ctx.send("Reason must be <= 100 characters", ephemeral=True) - # return - # if not channel: - # channel = ctx.channel - # - # # role = ctx.guild.default_role # Uncomment once implemented - # if isinstance(channel, GuildText): - # to_deny = Permissions.SEND_MESSAGES - # elif isinstance(channel, GuildVoice): - # to_deny = Permissions.CONNECT | Permissions.SPEAK - # - # overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny) - # # TODO: Get original permissions - # # TODO: Apply overwrite - # overwrite = overwrite - # _ = Lock( - # channel=channel.id, - # guild=ctx.guild.id, - # admin=ctx.author.id, - # reason=reason, - # duration=duration, - # ) # .save() # Uncomment once implemented - # # await ctx.send(f"{channel.mention} locked for {duration} minute(s)") - # await ctx.send("Unfortunately, this is not yet implemented", hidden=True) - # - # @cog_ext.cog_slash( - # name="unlock", - # description="Unlocks a channel", - # choices=[ - # create_option( - # name="channel", - # description="Channel to lock", - # opt_type=7, - # required=False, - # ), - # ], - # ) - # @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) - # async def _unlock( - # self, - # ctx: InteractionContext, - # channel: Union[GuildText, GuildVoice] = None, - # ) -> None: - # if not channel: - # channel = ctx.channel - # lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first() - # if not lock: - # await ctx.send(f"{channel.mention} not locked.", ephemeral=True) - # return - # for role in ctx.guild.roles: - # with suppress(Exception): - # await self._unlock_channel(channel, role, ctx.author) - # lock.active = False - # lock.save() - # await ctx.send(f"{channel.mention} unlocked") +# +# +# class LockCog(Scale): +# """J.A.R.V.I.S. LockCog.""" +# +# @slash_command(name="lock", description="Lock a channel") +# @slash_option(name="reason", +# description="Lock Reason", +# opt_type=3, +# required=True,) +# @slash_option(name="duration", +# description="Lock duration in minutes (default 10)", +# opt_type=4, +# required=False,) +# @slash_option(name="channel", +# description="Channel to lock", +# opt_type=7, +# required=False,) +# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) +# async def _lock( +# self, +# ctx: InteractionContext, +# reason: str, +# duration: int = 10, +# channel: Union[GuildText, GuildVoice] = None, +# ) -> None: +# await ctx.defer(ephemeral=True) +# if duration <= 0: +# await ctx.send("Duration must be > 0", ephemeral=True) +# return +# +# elif duration > 60 * 12: +# await ctx.send("Duration must be <= 12 hours", ephemeral=True) +# return +# +# if len(reason) > 100: +# await ctx.send("Reason must be <= 100 characters", ephemeral=True) +# return +# if not channel: +# channel = ctx.channel +# +# # role = ctx.guild.default_role # Uncomment once implemented +# if isinstance(channel, GuildText): +# to_deny = Permissions.SEND_MESSAGES +# elif isinstance(channel, GuildVoice): +# to_deny = Permissions.CONNECT | Permissions.SPEAK +# +# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny) +# # TODO: Get original permissions +# # TODO: Apply overwrite +# overwrite = overwrite +# _ = Lock( +# channel=channel.id, +# guild=ctx.guild.id, +# admin=ctx.author.id, +# reason=reason, +# duration=duration, +# ) # .save() # Uncomment once implemented +# # await ctx.send(f"{channel.mention} locked for {duration} minute(s)") +# await ctx.send("Unfortunately, this is not yet implemented", hidden=True) +# +# @cog_ext.cog_slash( +# name="unlock", +# description="Unlocks a channel", +# choices=[ +# create_option( +# name="channel", +# description="Channel to lock", +# opt_type=7, +# required=False, +# ), +# ], +# ) +# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) +# async def _unlock( +# self, +# ctx: InteractionContext, +# channel: Union[GuildText, GuildVoice] = None, +# ) -> None: +# if not channel: +# channel = ctx.channel +# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first() +# if not lock: +# await ctx.send(f"{channel.mention} not locked.", ephemeral=True) +# return +# for role in ctx.guild.roles: +# with suppress(Exception): +# await self._unlock_channel(channel, role, ctx.author) +# lock.active = False +# lock.save() +# await ctx.send(f"{channel.mention} unlocked") diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 13aa948..4be1910 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,122 +1,122 @@ """J.A.R.V.I.S. MuteCog.""" -from datetime import datetime - -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( - OptionTypes, - SlashCommandChoice, - slash_command, - slash_option, -) -from dis_snek.models.snek.command import check - -from jarvis.db.models import Mute -from jarvis.utils import build_embed -from jarvis.utils.permissions import admin_or_permissions - - -class MuteCog(Scale): - """J.A.R.V.I.S. MuteCog.""" - - def __init__(self, bot: Snake): - self.bot = bot - - @slash_command(name="mute", description="Mute a user") - @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) - @slash_option( - name="reason", - description="Reason for mute", - opt_type=OptionTypes.STRING, - required=True, - ) - @slash_option( - name="time", - description="Duration of mute, default 1", - opt_type=OptionTypes.INTEGER, - required=False, - ) - @slash_option( - name="scale", - description="Time scale, default Hour(s)", - opt_type=OptionTypes.INTEGER, - required=False, - choices=[ - SlashCommandChoice(name="Minute(s)", value=1), - SlashCommandChoice(name="Hour(s)", value=60), - SlashCommandChoice(name="Day(s)", value=3600), - SlashCommandChoice(name="Week(s)", value=604800), - ], - ) - @check( - admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS - ) - ) - async def _timeout( - self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 - ) -> None: - if user == ctx.author: - await ctx.send("You cannot mute yourself.", ephemeral=True) - return - if user == self.bot.user: - await ctx.send("I'm afraid I can't let you do that", ephemeral=True) - return - if len(reason) > 100: - await ctx.send("Reason must be < 100 characters", ephemeral=True) - return - - # Max 4 weeks (2419200 seconds) per API - duration = time * scale - if duration > 2419200: - await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) - return - - await user.timeout(communication_disabled_until=duration, reason=reason) - m = Mute( - user=user.id, - reason=reason, - admin=ctx.author.id, - guild=ctx.guild.id, - duration=duration, - active=True, - ) - await m.commit() - - embed = build_embed( - title="User Muted", - description=f"{user.mention} has been muted", - fields=[EmbedField(name="Reason", value=reason)], - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_thumbnail(url=user.display_avatar.url) - embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - await ctx.send(embed=embed) - - @slash_command(name="unmute", description="Unmute a user") - @slash_option( - name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True - ) - @check( - admin_or_permissions( - Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS - ) - ) - async def _unmute(self, ctx: InteractionContext, user: Member) -> None: - if ( - not user.communication_disabled_until - or user.communication_disabled_until < datetime.now() # noqa: W503 - ): - await ctx.send("User is not muted", ephemeral=True) - return - - embed = build_embed( - title="User Unmuted", - description=f"{user.mention} has been unmuted", - fields=[], - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_thumbnail(url=user.display_avatar.url) - embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") - await ctx.send(embed=embed) +# from datetime import datetime +# +# from dis_snek import InteractionContext, Permissions, Scale, Snake +# from dis_snek.models.discord.embed import EmbedField +# from dis_snek.models.discord.user import Member +# from dis_snek.models.snek.application_commands import ( +# OptionTypes, +# SlashCommandChoice, +# slash_command, +# slash_option, +# ) +# from dis_snek.models.snek.command import check +# +# from jarvis.db.models import Mute +# from jarvis.utils import build_embed +# from jarvis.utils.permissions import admin_or_permissions +# +# +# class MuteCog(Scale): +# """J.A.R.V.I.S. MuteCog.""" +# +# def __init__(self, bot: Snake): +# self.bot = bot +# +# @slash_command(name="mute", description="Mute a user") +# @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) +# @slash_option( +# name="reason", +# description="Reason for mute", +# opt_type=OptionTypes.STRING, +# required=True, +# ) +# @slash_option( +# name="time", +# description="Duration of mute, default 1", +# opt_type=OptionTypes.INTEGER, +# required=False, +# ) +# @slash_option( +# name="scale", +# description="Time scale, default Hour(s)", +# opt_type=OptionTypes.INTEGER, +# required=False, +# choices=[ +# SlashCommandChoice(name="Minute(s)", value=1), +# SlashCommandChoice(name="Hour(s)", value=60), +# SlashCommandChoice(name="Day(s)", value=3600), +# SlashCommandChoice(name="Week(s)", value=604800), +# ], +# ) +# @check( +# admin_or_permissions( +# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS +# ) +# ) +# async def _timeout( +# self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 +# ) -> None: +# if user == ctx.author: +# await ctx.send("You cannot mute yourself.", ephemeral=True) +# return +# if user == self.bot.user: +# await ctx.send("I'm afraid I can't let you do that", ephemeral=True) +# return +# if len(reason) > 100: +# await ctx.send("Reason must be < 100 characters", ephemeral=True) +# return +# +# # Max 4 weeks (2419200 seconds) per API +# duration = time * scale +# if duration > 2419200: +# await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) +# return +# +# await user.timeout(communication_disabled_until=duration, reason=reason) +# m = Mute( +# user=user.id, +# reason=reason, +# admin=ctx.author.id, +# guild=ctx.guild.id, +# duration=duration, +# active=True, +# ) +# await m.commit() +# +# embed = build_embed( +# title="User Muted", +# description=f"{user.mention} has been muted", +# fields=[EmbedField(name="Reason", value=reason)], +# ) +# embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) +# embed.set_thumbnail(url=user.display_avatar.url) +# embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") +# await ctx.send(embed=embed) +# +# @slash_command(name="unmute", description="Unmute a user") +# @slash_option( +# name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True +# ) +# @check( +# admin_or_permissions( +# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS +# ) +# ) +# async def _unmute(self, ctx: InteractionContext, user: Member) -> None: +# if ( +# not user.communication_disabled_until +# or user.communication_disabled_until < datetime.now() # noqa: W503 +# ): +# await ctx.send("User is not muted", ephemeral=True) +# return +# +# embed = build_embed( +# title="User Unmuted", +# description=f"{user.mention} has been unmuted", +# fields=[], +# ) +# embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) +# embed.set_thumbnail(url=user.display_avatar.url) +# embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") +# await ctx.send(embed=embed) From fa33a79de396329acfc706c989c426d2cb363204 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Feb 2022 11:50:47 -0700 Subject: [PATCH 091/365] Update ban --- jarvis/cogs/admin/ban.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index f1cd3a8..4db8461 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,8 +1,7 @@ """J.A.R.V.I.S. BanCog.""" import re -from datetime import datetime, timedelta -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -18,16 +17,12 @@ from jarvis_core.db import q from jarvis_core.db.models import Ban, Unban from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog from jarvis.utils.permissions import admin_or_permissions -class BanCog(CacheCog): +class BanCog(Scale): """J.A.R.V.I.S. BanCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - async def discord_apply_ban( self, ctx: InteractionContext, @@ -40,7 +35,7 @@ class BanCog(CacheCog): ) -> None: """Apply a Discord ban.""" await ctx.guild.ban(user, reason=reason) - _ = Ban( + b = Ban( user=user.id, username=user.username, discrim=user.discriminator, @@ -50,7 +45,8 @@ class BanCog(CacheCog): type=mtype, duration=duration, active=active, - ).save() + ) + await b.commit() embed = build_embed( title="User Banned", @@ -70,14 +66,15 @@ class BanCog(CacheCog): async def discord_apply_unban(self, ctx: InteractionContext, user: User, reason: str) -> None: """Apply a Discord unban.""" await ctx.guild.unban(user, reason=reason) - _ = Unban( + u = Unban( user=user.id, username=user.username, discrim=user.discriminator, guild=ctx.guild.id, admin=ctx.author.id, reason=reason, - ).save() + ) + await u.commit() embed = build_embed( title="User Unbanned", @@ -319,10 +316,10 @@ class BanCog(CacheCog): search["active"] = True if btype > 0: search["type"] = types[btype] - bans = Ban.objects(**search).order_by("-created_at") + bans = Ban.find(search).sort([("created_at", -1)]) db_bans = [] fields = [] - for ban in bans: + async for ban in bans: if not ban.username: user = await self.bot.fetch_user(ban.user) ban.username = user.username if user else "[deleted user]" @@ -379,14 +376,4 @@ class BanCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "guild": ctx.guild.id, - "user": ctx.author.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "btype": btype, - "active": active, - "paginator": paginator, - } - await paginator.send(ctx) From 10fe9384095fddd71bdc724b64f613e8b332d465 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Feb 2022 11:51:43 -0700 Subject: [PATCH 092/365] Use pastypy for logs --- jarvis/client.py | 13 ++++----- poetry.lock | 75 +++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 253abf0..c3ff3da 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,9 +1,7 @@ """Custom JARVIS client.""" -import json import traceback from datetime import datetime -from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField @@ -12,10 +10,11 @@ from jarvis_core.db import q from jarvis_core.db.models import Setting from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt +from pastypy import AsyncPaste as Paste DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 -DEFAULT_URL = "https://paste.zevs.me/documents" +DEFAULT_SITE = "https://paste.zevs.me" ERROR_MSG = """ Command Information: @@ -98,14 +97,12 @@ class Jarvis(Snake): if len(full_message) >= 1900: error_message = " ".join(traceback.format_exception(error)) full_message += "Exception: |\n " + error_message - async with ClientSession() as session: - resp = await session.post(DEFAULT_URL, data=full_message) - data = await resp.read() - data = json.loads(data.decode("UTF8")) + paste = Paste(content=full_message) + await paste.save(DEFAULT_SITE) await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." - f"\nPlease see log at https://paste.zevs.me/{data['key']}" + f"\nPlease see log at {paste.url}" ) else: error_message = "".join(traceback.format_exception(error)) diff --git a/poetry.lock b/poetry.lock index 830b51d..e84ca98 100644 --- a/poetry.lock +++ b/poetry.lock @@ -106,7 +106,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -377,6 +377,29 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "pastypy" +version = "1.0.1" +description = "" +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} +aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} +async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} +pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} +requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} +urllib3 = {version = "1.26.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version < \"4\""} +yarl = {version = "1.7.2", markers = "python_version >= \"3.6\""} + [[package]] name = "pathspec" version = "0.9.0" @@ -436,6 +459,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "pycryptodome" +version = "3.14.1" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "pydocstyle" version = "6.1.1" @@ -748,7 +779,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "d34963008bb31a5168210290f44909aa684a363455b2d59fe792f41918ec4705" +content-hash = "2adcfd60566d51e43a6f5a3ee0f96140a38e91401800916042cc7cd7e6adb37d" [metadata.files] aiohttp = [ @@ -875,8 +906,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, @@ -1160,6 +1191,10 @@ parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] +pastypy = [ + {file = "pastypy-1.0.1-py3-none-any.whl", hash = "sha256:63cc664568f86f6ddeb7e5687422bbf4b338d067ea887ed240223c8cbcf6fd2d"}, + {file = "pastypy-1.0.1.tar.gz", hash = "sha256:0393d1635b5031170eae3efaf376b14c3a4af7737c778d7ba7d56f2bd25bf5b1"}, +] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, @@ -1247,6 +1282,38 @@ pycodestyle = [ {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] +pycryptodome = [ + {file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f403a3e297a59d94121cb3ee4b1cf41f844332940a62d71f9e4a009cc3533493"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ce7a875694cd6ccd8682017a7c06c6483600f151d8916f2b25cf7a439e600263"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a36ab51674b014ba03da7f98b675fcb8eabd709a2d8e18219f784aba2db73b72"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:50a5346af703330944bea503106cd50c9c2212174cfcb9939db4deb5305a8367"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:36e3242c4792e54ed906c53f5d840712793dc68b726ec6baefd8d978c5282d30"}, + {file = "pycryptodome-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c880a98376939165b7dc504559f60abe234b99e294523a273847f9e7756f4132"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dcd65355acba9a1d0fc9b923875da35ed50506e339b35436277703d7ace3e222"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:766a8e9832128c70012e0c2b263049506cbf334fb21ff7224e2704102b6ef59e"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2562de213960693b6d657098505fd4493c45f3429304da67efcbeb61f0edfe89"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d1b7739b68a032ad14c5e51f7e4e1a5f92f3628bba024a2bda1f30c481fc85d8"}, + {file = "pycryptodome-3.14.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:27e92c1293afcb8d2639baf7eb43f4baada86e4de0f1fb22312bfc989b95dae2"}, + {file = "pycryptodome-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f2772af1c3ef8025c85335f8b828d0193fa1e43256621f613280e2c81bfad423"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:9ec761a35dbac4a99dcbc5cd557e6e57432ddf3e17af8c3c86b44af9da0189c0"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e64738207a02a83590df35f59d708bf1e7ea0d6adce712a777be2967e5f7043c"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:e24d4ec4b029611359566c52f31af45c5aecde7ef90bf8f31620fd44c438efe7"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:8b5c28058102e2974b9868d72ae5144128485d466ba8739abd674b77971454cc"}, + {file = "pycryptodome-3.14.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:924b6aad5386fb54f2645f22658cb0398b1f25bc1e714a6d1522c75d527deaa5"}, + {file = "pycryptodome-3.14.1-cp35-abi3-win32.whl", hash = "sha256:53dedbd2a6a0b02924718b520a723e88bcf22e37076191eb9b91b79934fb2192"}, + {file = "pycryptodome-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:ea56a35fd0d13121417d39a83f291017551fa2c62d6daa6b04af6ece7ed30d84"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:028dcbf62d128b4335b61c9fbb7dd8c376594db607ef36d5721ee659719935d5"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:69f05aaa90c99ac2f2af72d8d7f185f729721ad7c4be89e9e3d0ab101b0ee875"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:12ef157eb1e01a157ca43eda275fa68f8db0dd2792bc4fe00479ab8f0e6ae075"}, + {file = "pycryptodome-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:f572a3ff7b6029dd9b904d6be4e0ce9e309dcb847b03e3ac8698d9d23bb36525"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9924248d6920b59c260adcae3ee231cd5af404ac706ad30aa4cd87051bf09c50"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e0c04c41e9ade19fbc0eff6aacea40b831bfcb2c91c266137bcdfd0d7b2f33ba"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:893f32210de74b9f8ac869ed66c97d04e7d351182d6d39ebd3b36d3db8bda65d"}, + {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"}, + {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"}, +] pydocstyle = [ {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, diff --git a/pyproject.toml b/pyproject.toml index 98287ba..354687e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ tweepy = "^4.5.0" orjson = "^3.6.6" jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} aiohttp = "^3.8.1" +pastypy = "^1.0.1" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 0b3c2e712187710873664c7bd483f90a33f2a71a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Feb 2022 21:53:16 -0700 Subject: [PATCH 093/365] Move modlog and events into jarvis.client --- jarvis/__init__.py | 8 - jarvis/client.py | 363 ++++++++++++++++++++++++++++++--- jarvis/cogs/modlog/__init__.py | 11 - jarvis/cogs/modlog/member.py | 1 - jarvis/cogs/modlog/message.py | 112 ---------- jarvis/cogs/modlog/utils.py | 34 --- jarvis/events/member.py | 23 --- jarvis/events/message.py | 228 --------------------- jarvis/utils/__init__.py | 32 ++- 9 files changed, 367 insertions(+), 445 deletions(-) delete mode 100644 jarvis/cogs/modlog/__init__.py delete mode 100644 jarvis/cogs/modlog/member.py delete mode 100644 jarvis/cogs/modlog/message.py delete mode 100644 jarvis/cogs/modlog/utils.py delete mode 100644 jarvis/events/member.py delete mode 100644 jarvis/events/message.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 45d3232..78628f8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -8,7 +8,6 @@ from jarvis_core.db import connect from jarvis import utils from jarvis.client import Jarvis from jarvis.config import get_config -from jarvis.events import member, message jconfig = get_config() @@ -36,11 +35,4 @@ def run() -> None: jarvis.load_extension(extension) jarvis.max_messages = jconfig.max_messages - - # Add event listeners - if jconfig.events: - _ = [ - member.MemberEventHandler(jarvis), - message.MessageEventHandler(jarvis), - ] jarvis.start(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index c3ff3da..f7e6df3 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,13 +1,19 @@ """Custom JARVIS client.""" +import re import traceback from datetime import datetime from dis_snek import Snake, listen +from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.message import Message +from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext from jarvis_core.db import q -from jarvis_core.db.models import Setting +from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning +from jarvis_core.filters import invites from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste @@ -45,32 +51,6 @@ class Jarvis(Snake): "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) - async def on_command(self, ctx: InteractionContext) -> None: - """Lepton on_command override.""" - if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: - modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) - if modlog: - channel = await ctx.guild.fetch_channel(modlog.value) - args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items()) - fields = [ - EmbedField( - name="Command", - value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", - inline=False, - ), - ] - embed = build_embed( - title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", - fields=fields, - color="#fc9e3f", - ) - embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) - embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" - ) - await channel.send(embed=embed) - async def on_command_error( self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: @@ -113,3 +93,332 @@ class Jarvis(Snake): ) await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) return await super().on_command_error(ctx, error, *args, **kwargs) + + # Modlog + async def on_command(self, ctx: InteractionContext) -> None: + """Lepton on_command override.""" + if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: + modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) + if modlog: + channel = await ctx.guild.fetch_channel(modlog.value) + args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items()) + fields = [ + EmbedField( + name="Command", + value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", + inline=False, + ), + ] + embed = build_embed( + title="Command Invoked", + description=f"{ctx.author.mention} invoked a command", + fields=fields, + color="#fc9e3f", + ) + embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) + embed.set_footer( + text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + ) + await channel.send(embed=embed) + + async def on_message_edit(self, event: MessageUpdate) -> None: + """Process on_message_edit events.""" + before = event.before + after = event.after + if not before.author.bot: + modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) + if modlog: + if before.content == after.content or before.content is None: + return + channel = before.guild.get_channel(modlog.value) + fields = [ + EmbedField( + "Original Message", + before.content if before.content else "N/A", + False, + ), + EmbedField( + "New Message", + after.content if after.content else "N/A", + False, + ), + ] + embed = build_embed( + title="Message Edited", + description=f"{before.author.mention} edited a message", + fields=fields, + color="#fc9e3f", + timestamp=after.edited_timestamp, + url=after.jump_url, + ) + embed.set_author( + name=before.author.username, + icon_url=before.author.display_avatar.url, + url=after.jump_url, + ) + embed.set_footer( + text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" + ) + await channel.send(embed=embed) + if not isinstance(after.channel, DMChannel) and not after.author.bot: + await self.massmention(after) + await self.roleping(after) + await self.checks(after) + await self.roleping(after) + await self.checks(after) + + async def on_message_delete(self, event: MessageDelete) -> None: + """Process on_message_delete events.""" + message = event.message + modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) + if modlog: + fields = [EmbedField("Original Message", message.content or "N/A", False)] + + if message.attachments: + value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) + fields.append( + EmbedField( + name="Attachments", + value=value, + inline=False, + ) + ) + + if message.sticker_items: + value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) + fields.append( + EmbedField( + name="Stickers", + value=value, + inline=False, + ) + ) + + if message.embeds: + value = str(len(message.embeds)) + " embeds" + fields.append( + EmbedField( + name="Embeds", + value=value, + inline=False, + ) + ) + + channel = message.guild.get_channel(modlog.value) + embed = build_embed( + title="Message Deleted", + description=f"{message.author.mention}'s message was deleted", + fields=fields, + color="#fc9e3f", + ) + + embed.set_author( + name=message.author.username, + icon_url=message.author.display_avatar.url, + url=message.jump_url, + ) + embed.set_footer( + text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" + ) + await channel.send(embed=embed) + + # Events + async def on_member_join(self, user: Member) -> None: + """Handle on_member_join event.""" + guild = user.guild + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) + if unverified: + role = guild.get_role(unverified.value) + if role not in user.roles: + await user.add_roles(role, reason="User just joined and is unverified") + + async def autopurge(self, message: Message) -> None: + """Handle autopurge events.""" + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) + if autopurge: + await message.delete(delay=autopurge.delay) + + async def autoreact(self, message: Message) -> None: + """Handle autoreact events.""" + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) + if autoreact: + for reaction in autoreact.reactions: + await message.add_reaction(reaction) + + async def checks(self, message: Message) -> None: + """Other message checks.""" + # #tech + channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) + if channel and message.author.id == 293795462752894976: + await channel.send( + content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 + ) + content = re.sub(r"\s+", "", message.content) + match = invites.search(content) + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) + if not setting: + setting = Setting(guild=message.guild.id, setting="noinvite", value=True) + await setting.commit() + if match: + guild_invites = await message.guild.invites() + allowed = [x.code for x in guild_invites] + [ + "dbrand", + "VtgZntXcnZ", + "gPfYGbvTCE", + ] + if match.group(1) not in allowed and setting.value: + await message.delete() + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Sent an invite link", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField( + name="Reason", + value="Sent an invite link", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 + ) + await message.channel.send(embed=embed) + + async def massmention(self, message: Message) -> None: + """Handle massmention events.""" + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) + if ( + massmention + and massmention.value > 0 # noqa: W503 + and len(message.mentions) # noqa: W503 + - (1 if message.author in message.mentions else 0) # noqa: W503 + > massmention.value # noqa: W503 + ): + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Mass Mention", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def roleping(self, message: Message) -> None: + """Handle roleping events.""" + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) + + if not rolepings: + return + + # Get all role IDs involved with message + roles = [] + for mention in message.role_mentions: + roles.append(mention.id) + for mention in message.mentions: + for role in mention.roles: + roles.append(role.id) + + if not roles: + return + + # Get all roles that are rolepinged + roleping_ids = [r.role for r in rolepings] + + # Get roles in rolepings + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) + + # Check if the user has the role, so they are allowed to ping it + user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) + + # Admins can ping whoever + user_is_admin = message.author.guild_permissions.ADMINISTRATOR + + # Check if user in a bypass list + user_has_bypass = False + for roleping in rolepings: + if message.author.id in roleping.bypass["users"]: + user_has_bypass = True + break + if any(role.id in roleping.bypass["roles"] for role in message.author.roles): + user_has_bypass = True + break + + if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Pinged a blocked role/user with a blocked role", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField( + name="Reason", + value="Pinged a blocked role/user with a blocked role", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def on_message(self, event: MessageCreate) -> None: + """Handle on_message event. Calls other event handlers.""" + message = event.message + if not isinstance(message.channel, DMChannel) and not message.author.bot: + await self.autoreact(message) + await self.massmention(message) + await self.roleping(message) + await self.autopurge(message) + await self.checks(message) diff --git a/jarvis/cogs/modlog/__init__.py b/jarvis/cogs/modlog/__init__.py deleted file mode 100644 index f59f2c3..0000000 --- a/jarvis/cogs/modlog/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""J.A.R.V.I.S. Modlog Cogs.""" -from dis_snek import Snake - -from jarvis.cogs.modlog import command, message - - -def setup(bot: Snake) -> None: - """Add modlog cogs to J.A.R.V.I.S.""" - command.ModlogCommandCog(bot) - # member.ModlogMemberCog(bot) - message.ModlogMessageCog(bot) diff --git a/jarvis/cogs/modlog/member.py b/jarvis/cogs/modlog/member.py deleted file mode 100644 index b00514e..0000000 --- a/jarvis/cogs/modlog/member.py +++ /dev/null @@ -1 +0,0 @@ -"""JARVIS member modlog processing.""" diff --git a/jarvis/cogs/modlog/message.py b/jarvis/cogs/modlog/message.py deleted file mode 100644 index 848a929..0000000 --- a/jarvis/cogs/modlog/message.py +++ /dev/null @@ -1,112 +0,0 @@ -"""J.A.R.V.I.S. ModlogMessageCog.""" -from dis_snek import Scale, Snake, listen -from dis_snek.api.events.discord import MessageDelete, MessageUpdate -from dis_snek.models.discord.embed import EmbedField -from jarvis_core.db import q -from jarvis_core.db.models import Setting - -from jarvis.utils import build_embed - - -class ModlogMessageCog(Scale): - """J.A.R.V.I.S. ModlogMessageCog.""" - - def __init__(self, bot: Snake): - self.bot = bot - - @listen() - async def on_message_edit(self, event: MessageUpdate) -> None: - """Process on_message_edit events.""" - before = event.before - after = event.after - if not before.author.bot: - modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) - if modlog: - if before.content == after.content or before.content is None: - return - channel = before.guild.get_channel(modlog.value) - fields = [ - EmbedField( - "Original Message", - before.content if before.content else "N/A", - False, - ), - EmbedField( - "New Message", - after.content if after.content else "N/A", - False, - ), - ] - embed = build_embed( - title="Message Edited", - description=f"{before.author.mention} edited a message", - fields=fields, - color="#fc9e3f", - timestamp=after.edited_timestamp, - url=after.jump_url, - ) - embed.set_author( - name=before.author.username, - icon_url=before.author.display_avatar.url, - url=after.jump_url, - ) - embed.set_footer( - text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" - ) - await channel.send(embed=embed) - - @listen() - async def on_message_delete(self, event: MessageDelete) -> None: - """Process on_message_delete events.""" - message = event.message - modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) - if modlog: - fields = [EmbedField("Original Message", message.content or "N/A", False)] - - if message.attachments: - value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) - fields.append( - EmbedField( - name="Attachments", - value=value, - inline=False, - ) - ) - - if message.sticker_items: - value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) - fields.append( - EmbedField( - name="Stickers", - value=value, - inline=False, - ) - ) - - if message.embeds: - value = str(len(message.embeds)) + " embeds" - fields.append( - EmbedField( - name="Embeds", - value=value, - inline=False, - ) - ) - - channel = message.guild.get_channel(modlog.value) - embed = build_embed( - title="Message Deleted", - description=f"{message.author.mention}'s message was deleted", - fields=fields, - color="#fc9e3f", - ) - - embed.set_author( - name=message.author.username, - icon_url=message.author.display_avatar.url, - url=message.jump_url, - ) - embed.set_footer( - text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" - ) - await channel.send(embed=embed) diff --git a/jarvis/cogs/modlog/utils.py b/jarvis/cogs/modlog/utils.py deleted file mode 100644 index 72b5cbe..0000000 --- a/jarvis/cogs/modlog/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -"""J.A.R.V.I.S. Modlog Cog Utilities.""" -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.guild import AuditLogEntry -from dis_snek.models.discord.user import Member - -from jarvis.utils import build_embed - - -def modlog_embed( - member: Member, - admin: Member, - log: AuditLogEntry, - title: str, - desc: str, -) -> Embed: - """Get modlog embed.""" - fields = [ - EmbedField( - name="Moderator", - value=f"{admin.mention} ({admin.username}#{admin.discriminator})", - ), - ] - if log.reason: - fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) - embed = build_embed( - title=title, - description=desc, - color="#fc9e3f", - fields=fields, - timestamp=log.created_at, - ) - embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) - embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") - return embed diff --git a/jarvis/events/member.py b/jarvis/events/member.py deleted file mode 100644 index f7e4ada..0000000 --- a/jarvis/events/member.py +++ /dev/null @@ -1,23 +0,0 @@ -"""J.A.R.V.I.S. Member event handler.""" -from dis_snek import Snake, listen -from dis_snek.models.discord.user import Member -from jarvis_core.db import q -from jarvis_core.db.models import Setting - - -class MemberEventHandler(object): - """J.A.R.V.I.S. Member event handler.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_member_join) - - @listen() - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild - unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) - if unverified: - role = guild.get_role(unverified.value) - if role not in user.roles: - await user.add_roles(role, reason="User just joined and is unverified") diff --git a/jarvis/events/message.py b/jarvis/events/message.py deleted file mode 100644 index 693bc3c..0000000 --- a/jarvis/events/message.py +++ /dev/null @@ -1,228 +0,0 @@ -"""J.A.R.V.I.S. Message event handler.""" -import re - -from dis_snek import Snake, listen -from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.models.discord.channel import DMChannel -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.message import Message -from jarvis_core.db import q -from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning - -import jarvis -from jarvis.utils import build_embed - -invites = re.compile( - r"(?:https?://)?(?:www.)?(?:discord.(?:gg|io|me|li)|discord(?:app)?.com/invite)/([^\s/]+?)(?=\b)", # noqa: E501 - flags=re.IGNORECASE, -) - - -class MessageEventHandler(object): - """J.A.R.V.I.S. Message event handler.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.bot.add_listener(self.on_message) - self.bot.add_listener(self.on_message_edit) - - async def autopurge(self, message: Message) -> None: - """Handle autopurge events.""" - autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) - if autopurge: - await message.delete(delay=autopurge.delay) - - async def autoreact(self, message: Message) -> None: - """Handle autoreact events.""" - autoreact = await Autoreact.find_one( - q( - guild=message.guild.id, - channel=message.channel.id, - ) - ) - if autoreact: - for reaction in autoreact.reactions: - await message.add_reaction(reaction) - - async def checks(self, message: Message) -> None: - """Other message checks.""" - # #tech - channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) - if channel and message.author.id == 293795462752894976: - await channel.send( - content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 - ) - content = re.sub(r"\s+", "", message.content) - match = invites.search(content) - setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) - if not setting: - setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - await setting.commit() - if match: - guild_invites = await message.guild.invites() - allowed = [x.code for x in guild_invites] + [ - "dbrand", - "VtgZntXcnZ", - "gPfYGbvTCE", - ] - if match.group(1) not in allowed and setting.value: - await message.delete() - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Sent an invite link", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) - await message.channel.send(embed=embed) - - async def massmention(self, message: Message) -> None: - """Handle massmention events.""" - massmention = await Setting.find_one( - q( - guild=message.guild.id, - setting="massmention", - ) - ) - if ( - massmention - and massmention.value > 0 # noqa: W503 - and len(message.mentions) # noqa: W503 - - (1 if message.author in message.mentions else 0) # noqa: W503 - > massmention.value # noqa: W503 - ): - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Mass Mention", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - async def roleping(self, message: Message) -> None: - """Handle roleping events.""" - rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) - - if not rolepings: - return - - # Get all role IDs involved with message - roles = [] - for mention in message.role_mentions: - roles.append(mention.id) - for mention in message.mentions: - for role in mention.roles: - roles.append(role.id) - - if not roles: - return - - # Get all roles that are rolepinged - roleping_ids = [r.role for r in rolepings] - - # Get roles in rolepings - role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) - - # Check if the user has the role, so they are allowed to ping it - user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) - - # Admins can ping whoever - user_is_admin = message.author.guild_permissions.ADMINISTRATOR - - # Check if user in a bypass list - user_has_bypass = False - for roleping in rolepings: - if message.author.id in roleping.bypass["users"]: - user_has_bypass = True - break - if any(role.id in roleping.bypass["roles"] for role in message.author.roles): - user_has_bypass = True - break - - if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = Warning( - active=True, - admin=jarvis.jconfig.client_id, - duration=24, - guild=message.guild.id, - reason="Pinged a blocked role/user with a blocked role", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - @listen() - async def on_message(self, message: Message) -> None: - """Handle on_message event. Calls other event handlers.""" - if not isinstance(message.channel, DMChannel) and not message.author.bot: - await self.autoreact(message) - await self.massmention(message) - await self.roleping(message) - await self.autopurge(message) - await self.checks(message) - - @listen() - async def on_message_edit(self, before: Message, after: Message) -> None: - """Handle on_message_edit event. Calls other event handlers.""" - if not isinstance(after.channel, DMChannel) and not after.author.bot: - await self.massmention(after) - await self.roleping(after) - await self.checks(after) - await self.roleping(after) - await self.checks(after) diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 009d24b..ad04b08 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -3,7 +3,9 @@ from datetime import datetime from pkgutil import iter_modules import git -from dis_snek.models.discord.embed import Embed +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.guild import AuditLogEntry +from dis_snek.models.discord.user import Member import jarvis.cogs import jarvis.db @@ -35,6 +37,34 @@ def build_embed( return embed +def modlog_embed( + member: Member, + admin: Member, + log: AuditLogEntry, + title: str, + desc: str, +) -> Embed: + """Get modlog embed.""" + fields = [ + EmbedField( + name="Moderator", + value=f"{admin.mention} ({admin.username}#{admin.discriminator})", + ), + ] + if log.reason: + fields.append(EmbedField(name="Reason", value=log.reason, inline=False)) + embed = build_embed( + title=title, + description=desc, + color="#fc9e3f", + fields=fields, + timestamp=log.created_at, + ) + embed.set_author(name=f"{member.username}", icon_url=member.display_avatar.url) + embed.set_footer(text=f"{member.username}#{member.discriminator} | {member.id}") + return embed + + def get_extensions(path: str = jarvis.cogs.__path__) -> list: """Get J.A.R.V.I.S. cogs.""" config = get_config() From caa94b03126611795ece96c79ba06b326f57bc24 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 8 Mar 2022 07:55:44 -0700 Subject: [PATCH 094/365] Fix get_owner -> fetch_owner --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 936c855..aaa8d53 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -219,7 +219,7 @@ class UtilCog(Scale): async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild - owner = await guild.get_owner() + owner = await guild.fetch_owner() owner = f"{owner.username}#{owner.discriminator}" if owner else "||`[redacted]`||" From eba4f1fff0e141dfdb03e2136bd6602e8ce5837b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 10 Mar 2022 22:42:34 -0700 Subject: [PATCH 095/365] Add LICENSE, PRIVACY, TERMS --- .pre-commit-config.yaml | 4 +-- LICENSE | 13 ++++++++++ PRIVACY.md | 55 +++++++++++++++++++++++++++++++++++++++++ TERMS.md | 15 +++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 PRIVACY.md create mode 100644 TERMS.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b88c6fe..045ddd0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: language_version: python3.10 - repo: https://github.com/pre-commit/mirrors-isort - rev: V5.10.1 + rev: v5.10.1 hooks: - id: isort args: ["--profile", "black"] @@ -37,7 +37,7 @@ repos: - id: flake8 additional_dependencies: - flake8-annotations~=2.0 - - flake8-bandit~=2.1 + #- flake8-bandit~=2.1 - flake8-docstrings~=1.5 - flake8-bugbear - flake8-comprehensions diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9a5300f --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +“Commons Clause” License Condition v1.0 + +The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. + +Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. + +For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. + +Software: JARVIS + +License: Expat License + +Licensor: zevaryx diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..9c77c7e --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,55 @@ +# Privacy Policy +Your privacy is important to us. It is JARVIS' policy to respect your privacy and comply with any applicable law and regulation regarding any personal information we may collect about you through our JARVIS bot. + +This policy is effective as of 20 March 2022 and was last updated on 10 March 2022. + +## Information We Collect +Information we collect includes both information you knowingly and actively provide us when using or participating in any of our services and promotions, and any information automatically sent by your devices in the course of accessing our products and services. + +## Log Data +When you use our JARVIS services, if opted in to usage data collection services, we may collect data in certain cicumstances: + +- Administrative activity (i.e. ban, warn, mute) + - Your User ID (either as admin or as the recipient of the activity) + - Guild ID of activity origin + - Your discriminator at time of activity (bans only) + - Your username at time of activity (bans only) +- Admin commands + - User ID of admin who executes admin command +- Reminders + - Your User ID + - The guild in which the command originated + - The channel in which the command originated + - Private text entered via the command +- Starboard + - Message ID of starred message + - Channel ID of starred message + - Guild ID of origin guild +- Automated activity logging + - We store no information about users who edit nicknames, join/leave servers, or other related activities. However, this information, if configured by server admins, is relayed into a Discord channel and is not automatically deleted, nor do we have control over this information. + - This information is also stored by Discord via their Audit Log, which we also have no control over. Please contact Discord for their own privacy policy and asking about your rights on their platform. + +## Use of Information +We use the information we collect to provide, maintain, and improve our services. Common uses where this data may be used includes sending reminders, helping administrate Discord servers, and providing extra utilities into Discord based on user content. + +## Security of Your Personal Information +Although we will do our best to protect the personal information you provide to us, we advise that no method of electronic transmission or storage is 100% secure, and no one can guarantee absolute data security. We will comply with laws applicable to us in respect of any data breach. + +## How Long We Keep Your Personal Information +We keep your personal information only for as long as we need to. This time period may depend on what we are using your information for, in accordance with this privacy policy. If your personal information is no longer required, we will delete it or make it anonymous by removing all details that identify you. + +## Your Rights and Controlling Your Personal Information +You may request access to your personal information, and change what you are okay with us collecting from you. You may also request that we delete your personal identifying information. Please message **zevaryx#5779** on Discord, or join the Discord server at https://discord.gg/4TuFvW5n and ask in there. + +## Limits of Our Policy +Our website may link to external sites that are not operated by us (ie. discord.com). Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices. + +## Changes to This Policy +At our discretion, we may change our privacy policy to reflect updates to our business processes, current acceptable practices, or legislative or regulatory changes. If we decide to change this privacy policy, we will post the changes here at the same link by which you are accessing this privacy policy. + +## Contact Us +For any questions or concerns regarding your privacy, you may contact us using the following details: + +### Discord +#### zevaryx#5779 +#### https://discord.gg/4TuFvW5n diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..4182214 --- /dev/null +++ b/TERMS.md @@ -0,0 +1,15 @@ +# Terms Of Use +Please just be reasonable do not try to mess with the bot in a malicious way. This includes but is not limited to: + +- Spamming +- Flooding +- Hacking +- DOS Attacks + +## Contact Us +For any questions or concerns regarding the terms, feel free to contact us: + +### Discord + +#### zevaryx#5779 +#### https://discord.gg/4TuFvW5n From 06ab5d105fa146d93ed3dc32f5e5a4a98b12caf1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:20:04 -0600 Subject: [PATCH 096/365] Add anti-phishing, ref #111 --- jarvis/client.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index f7e6df3..79f7ffe 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -3,9 +3,12 @@ import re import traceback from datetime import datetime +from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate from dis_snek.client.utils.misc_utils import find, find_all +from dis_snek.ext.tasks.task import Task +from dis_snek.ext.tasks.triggers import TimeTrigger from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.message import Message @@ -13,7 +16,7 @@ from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis_core.filters import invites +from jarvis_core.filters import invites, url from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste @@ -41,9 +44,34 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): + def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 + super().__init__(*args, **kwargs) + self.phishing_domains = set() + + @Task.create(TimeTrigger()) + async def _update_domains(self) -> None: + async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: + response = await session.get("https://phish.sinking.yachts/v2/recent/86415") + response.raise_for_status() + data = await response.json() + + for update in data: + if update["type"] == "add": + self.phishing_domains.add(update["domain"]) + elif update["type"] == "delete": + self.phishing_domains.discard(update["domain"]) + + async def _sync_domains(self) -> None: + async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: + response = await session.get("https://phish.sinking.yachts/v2/all") + response.raise_for_status() + self.phishing_domains = set(await response.json()) + @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" + await self._sync_domains() + self._update_domains.start() print("Logged in as {}".format(self.user)) # noqa: T001 print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 print( # noqa: T001 @@ -413,6 +441,34 @@ class Jarvis(Snake): ) await message.channel.send(embed=embed) + async def phishing(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.findall(message.content): + if match in self.phishing_domains: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Phishing URL", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Phishing URL", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" message = event.message @@ -422,3 +478,4 @@ class Jarvis(Snake): await self.roleping(message) await self.autopurge(message) await self.checks(message) + await self.phishing(message) From 89907026cdaee832ca894842390f15d1ad36a09b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:36:54 -0600 Subject: [PATCH 097/365] Add unsafe URL checker via spoopy detector --- jarvis/client.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 79f7ffe..e05e958 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -468,6 +468,48 @@ class Jarvis(Snake): text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" ) await message.channel.send(embed=embed) + await message.delete() + + async def malicious_url(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.findall(message.content): + async with ClientSession() as session: + resp = await session.get( + "https://spoopy.oceanlord.me/api/check_website", json={"website": match} + ) + if resp.status != 200: + break + data = await resp.json() + for item in data["processed"]["urls"].values(): + if not item["safe"]: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Unsafe URL", + user=message.author.id, + ) + await w.commit() + reasons = ", ".join(item["not_safe_reasons"]) + fields = [ + EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.nick if message.author.nick else message.author.name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + break async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" @@ -479,3 +521,4 @@ class Jarvis(Snake): await self.autopurge(message) await self.checks(message) await self.phishing(message) + await self.malicious_url(message) From 60ab06b544727d1628c82435a0f73c1f7339316a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 17 Mar 2022 15:52:26 -0600 Subject: [PATCH 098/365] Remove phishing sync, rely on external API for now --- jarvis/__init__.py | 4 ++-- jarvis/client.py | 13 ++++++------- poetry.lock | 14 +++++++------- run.py | 4 +++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 78628f8..38c27d8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -26,7 +26,7 @@ jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.s __version__ = "2.0.0a1" -def run() -> None: +async def run() -> None: """Run J.A.R.V.I.S.""" connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") jconfig.get_db_config() @@ -35,4 +35,4 @@ def run() -> None: jarvis.load_extension(extension) jarvis.max_messages = jconfig.max_messages - jarvis.start(jconfig.token) + await jarvis.astart(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index e05e958..921e999 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -7,13 +7,12 @@ from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import TimeTrigger from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.message import Message from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext +from dis_snek.models.snek.tasks import Task, TimeTrigger from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning from jarvis_core.filters import invites, url @@ -70,8 +69,8 @@ class Jarvis(Snake): @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" - await self._sync_domains() - self._update_domains.start() + # await self._sync_domains() + # self._update_domains.start() print("Logged in as {}".format(self.user)) # noqa: T001 print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 print( # noqa: T001 @@ -501,16 +500,17 @@ class Jarvis(Snake): fields=fields, ) embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, + name=message.author.display_name, icon_url=message.author.display_avatar.url, ) embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" ) await message.channel.send(embed=embed) await message.delete() break + @listen() async def on_message(self, event: MessageCreate) -> None: """Handle on_message event. Calls other event handlers.""" message = event.message @@ -520,5 +520,4 @@ class Jarvis(Snake): await self.roleping(message) await self.autopurge(message) await self.checks(message) - await self.phishing(message) await self.malicious_url(message) diff --git a/poetry.lock b/poetry.lock index e84ca98..2d8bfcf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -214,7 +214,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.5.0" +version = "0.6.0" description = "" category = "main" optional = false @@ -232,7 +232,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "f392757bb773cfeb9b3f14d581a599d13ee6e891" +resolved_reference = "8e3276a81575927e4484dfaa5c3e0e894e3c4707" [[package]] name = "jedi" @@ -555,18 +555,18 @@ test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] [[package]] name = "python-lsp-server" -version = "1.3.3" +version = "1.4.0" description = "Python Language Server for the Language Server Protocol" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] autopep8 = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"all\""} flake8 = {version = ">=4.0.0,<4.1.0", optional = true, markers = "extra == \"all\""} jedi = ">=0.17.2,<0.19.0" mccabe = {version = ">=0.6.0,<0.7.0", optional = true, markers = "extra == \"all\""} -pluggy = "*" +pluggy = ">=1.0.0" pycodestyle = {version = ">=2.8.0,<2.9.0", optional = true, markers = "extra == \"all\""} pydocstyle = {version = ">=2.0.0", optional = true, markers = "extra == \"all\""} pyflakes = {version = ">=2.4.0,<2.5.0", optional = true, markers = "extra == \"all\""} @@ -1444,8 +1444,8 @@ python-lsp-jsonrpc = [ {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, ] python-lsp-server = [ - {file = "python-lsp-server-1.3.3.tar.gz", hash = "sha256:1b48ccd8b70103522e8a8b9cb9ae1be2b27a5db0dfd661e7e44e6253ebefdc40"}, - {file = "python_lsp_server-1.3.3-py3-none-any.whl", hash = "sha256:ea7b1e623591ccbfbbf8e5dfe0df8119f27863125a58bdcacbacd1937d8e8cb3"}, + {file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"}, + {file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, diff --git a/run.py b/run.py index 7e6a73e..d53ee79 100755 --- a/run.py +++ b/run.py @@ -1,5 +1,7 @@ """Main run file for J.A.R.V.I.S.""" +import asyncio + from jarvis import run if __name__ == "__main__": - run() + asyncio.run(run()) From 526038c725122243d3e084445af5e4dd6d321ceb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 00:32:45 -0600 Subject: [PATCH 099/365] Re-add phishing sync, fix all client events, dual layer phishing check --- jarvis/client.py | 597 ++++++++++++++++++++++++----------------------- 1 file changed, 307 insertions(+), 290 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 921e999..5e75498 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -6,13 +6,15 @@ from datetime import datetime from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate -from dis_snek.client.utils.misc_utils import find, find_all +from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext -from dis_snek.models.snek.tasks import Task, TimeTrigger +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning from jarvis_core.filters import invites, url @@ -45,9 +47,9 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 super().__init__(*args, **kwargs) - self.phishing_domains = set() + self.phishing_domains = [] - @Task.create(TimeTrigger()) + @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/recent/86415") @@ -56,21 +58,23 @@ class Jarvis(Snake): for update in data: if update["type"] == "add": - self.phishing_domains.add(update["domain"]) + if update["domain"] not in self.phishing_domains: + self.phishing_domains.append(update["domain"]) elif update["type"] == "delete": - self.phishing_domains.discard(update["domain"]) + if update["domain"] in self.phishing_domains: + self.phishing_domains.remove(update["domain"]) async def _sync_domains(self) -> None: async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/all") response.raise_for_status() - self.phishing_domains = set(await response.json()) + self.phishing_domains = await response.json() @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" - # await self._sync_domains() - # self._update_domains.start() + await self._sync_domains() + self._update_domains.start() print("Logged in as {}".format(self.user)) # noqa: T001 print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 print( # noqa: T001 @@ -144,18 +148,301 @@ class Jarvis(Snake): ) embed.set_author(name=ctx.author.username, icon_url=ctx.author.display_avatar.url) embed.set_footer( - text=f"{ctx.author.username}#{ctx.author.discriminator} | {ctx.author.id}" + text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}" ) await channel.send(embed=embed) + # Events + async def on_member_join(self, user: Member) -> None: + """Handle on_member_join event.""" + guild = user.guild + unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) + if unverified: + role = guild.get_role(unverified.value) + if role not in user.roles: + await user.add_role(role, reason="User just joined and is unverified") + + async def autopurge(self, message: Message) -> None: + """Handle autopurge events.""" + autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) + if autopurge: + await message.delete(delay=autopurge.delay) + + async def autoreact(self, message: Message) -> None: + """Handle autoreact events.""" + autoreact = await Autoreact.find_one( + q( + guild=message.guild.id, + channel=message.channel.id, + ) + ) + if autoreact: + for reaction in autoreact.reactions: + await message.add_reaction(reaction) + + async def checks(self, message: Message) -> None: + """Other message checks.""" + # #tech + # channel = find(lambda x: x.id == 599068193339736096, message._mention_ids) + # if channel and message.author.id == 293795462752894976: + # await channel.send( + # content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 + # ) + content = re.sub(r"\s+", "", message.content) + match = invites.search(content) + setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) + if not setting: + setting = Setting(guild=message.guild.id, setting="noinvite", value=True) + await setting.commit() + if match: + guild_invites = await message.guild.invites() + guild_invites.append(message.guild.vanity_url_code) + allowed = [x.code for x in guild_invites] + [ + "dbrand", + "VtgZntXcnZ", + "gPfYGbvTCE", + ] + if match.group(1) not in allowed and setting.value: + await message.delete() + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Sent an invite link", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField( + name="Reason", + value="Sent an invite link", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" # noqa: E501 + ) + await message.channel.send(embed=embed) + + async def massmention(self, message: Message) -> None: + """Handle massmention events.""" + massmention = await Setting.find_one( + q( + guild=message.guild.id, + setting="massmention", + ) + ) + + if ( + massmention + and massmention.value > 0 # noqa: W503 + and len(message._mention_ids + message._mention_roles) # noqa: W503 + - (1 if message.author in message._mention_ids else 0) # noqa: W503 + > massmention.value # noqa: W503 + ): + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Mass Mention", + user=message.author.id, + ) + await w.commit() + fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def roleping(self, message: Message) -> None: + """Handle roleping events.""" + if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: + return + rolepings = Roleping.find(q(guild=message.guild.id, active=True)) + + # Get all role IDs involved with message + roles = [] + async for mention in message.mention_roles: + roles.append(mention.id) + async for mention in message.mention_users: + for role in mention.roles: + roles.append(role.id) + + if not roles: + return + + # Get all roles that are rolepinged + roleping_ids = [r.role for r in rolepings] + + # Get roles in rolepings + role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) + + # Check if the user has the role, so they are allowed to ping it + user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) + + # Admins can ping whoever + user_is_admin = message.author.has_permission(Permissions.ADMINISTRATOR) + + # Check if user in a bypass list + user_has_bypass = False + async for roleping in rolepings: + if message.author.id in roleping.bypass["users"]: + user_has_bypass = True + break + if any(role.id in roleping.bypass["roles"] for role in message.author.roles): + user_has_bypass = True + break + + if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Pinged a blocked role/user with a blocked role", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField( + name="Reason", + value="Pinged a blocked role/user with a blocked role", + inline=False, + ) + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + + async def phishing(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.finditer(message.content): + if match.group("domain") in self.phishing_domains: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Phishing URL", + user=message.author.id, + ) + await w.commit() + fields = [ + EmbedField(name="Reason", value="Phishing URL", inline=False), + EmbedField(name="Message ID", value=str(message.id)), + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + return True + return False + + async def malicious_url(self, message: Message) -> None: + """Check if the message contains any known phishing domains.""" + for match in url.finditer(message.content): + async with ClientSession() as session: + resp = await session.get( + "https://spoopy.oceanlord.me/api/check_website", json={"website": match.string} + ) + if resp.status != 200: + break + data = await resp.json() + for item in data["processed"]["urls"].values(): + if not item["safe"]: + w = Warning( + active=True, + admin=self.user.id, + duration=24, + guild=message.guild.id, + reason="Unsafe URL", + user=message.author.id, + ) + await w.commit() + reasons = ", ".join(item["not_safe_reasons"]) + fields = [ + EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False), + EmbedField(name="Message ID", valud=str(message.id)), + ] + embed = build_embed( + title="Warning", + description=f"{message.author.mention} has been warned", + fields=fields, + ) + embed.set_author( + name=message.author.display_name, + icon_url=message.author.display_avatar.url, + ) + embed.set_footer( + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + ) + await message.channel.send(embed=embed) + await message.delete() + return True + return False + + @listen() + async def on_message(self, event: MessageCreate) -> None: + """Handle on_message event. Calls other event handlers.""" + message = event.message + if not isinstance(message.channel, DMChannel) and not message.author.bot: + await self.autoreact(message) + await self.massmention(message) + await self.roleping(message) + await self.autopurge(message) + await self.checks(message) + if not await self.phishing(message): + await self.malicious_url(message) + + @listen() async def on_message_edit(self, event: MessageUpdate) -> None: """Process on_message_edit events.""" before = event.before after = event.after - if not before.author.bot: + if not after.author.bot: modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) if modlog: - if before.content == after.content or before.content is None: + if not before or before.content == after.content or before.content is None: return channel = before.guild.get_channel(modlog.value) fields = [ @@ -172,19 +459,19 @@ class Jarvis(Snake): ] embed = build_embed( title="Message Edited", - description=f"{before.author.mention} edited a message", + description=f"{after.author.mention} edited a message", fields=fields, color="#fc9e3f", timestamp=after.edited_timestamp, url=after.jump_url, ) embed.set_author( - name=before.author.username, - icon_url=before.author.display_avatar.url, + name=after.author.username, + icon_url=after.author.display_avatar.url, url=after.jump_url, ) embed.set_footer( - text=f"{before.author.username}#{before.author.discriminator} | {before.author.id}" + text=f"{after.author.user.username}#{after.author.discriminator} | {after.author.id}" ) await channel.send(embed=embed) if not isinstance(after.channel, DMChannel) and not after.author.bot: @@ -193,7 +480,10 @@ class Jarvis(Snake): await self.checks(after) await self.roleping(after) await self.checks(after) + if not await self.phishing(after): + await self.malicious_url(after) + @listen() async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" message = event.message @@ -245,279 +535,6 @@ class Jarvis(Snake): url=message.jump_url, ) embed.set_footer( - text=f"{message.author.username}#{message.author.discriminator} | {message.author.id}" + text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" ) await channel.send(embed=embed) - - # Events - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild - unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) - if unverified: - role = guild.get_role(unverified.value) - if role not in user.roles: - await user.add_roles(role, reason="User just joined and is unverified") - - async def autopurge(self, message: Message) -> None: - """Handle autopurge events.""" - autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) - if autopurge: - await message.delete(delay=autopurge.delay) - - async def autoreact(self, message: Message) -> None: - """Handle autoreact events.""" - autoreact = await Autoreact.find_one( - q( - guild=message.guild.id, - channel=message.channel.id, - ) - ) - if autoreact: - for reaction in autoreact.reactions: - await message.add_reaction(reaction) - - async def checks(self, message: Message) -> None: - """Other message checks.""" - # #tech - channel = find(lambda x: x.id == 599068193339736096, message.channel_mentions) - if channel and message.author.id == 293795462752894976: - await channel.send( - content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 - ) - content = re.sub(r"\s+", "", message.content) - match = invites.search(content) - setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) - if not setting: - setting = Setting(guild=message.guild.id, setting="noinvite", value=True) - await setting.commit() - if match: - guild_invites = await message.guild.invites() - allowed = [x.code for x in guild_invites] + [ - "dbrand", - "VtgZntXcnZ", - "gPfYGbvTCE", - ] - if match.group(1) not in allowed and setting.value: - await message.delete() - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Sent an invite link", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) - await message.channel.send(embed=embed) - - async def massmention(self, message: Message) -> None: - """Handle massmention events.""" - massmention = await Setting.find_one( - q( - guild=message.guild.id, - setting="massmention", - ) - ) - if ( - massmention - and massmention.value > 0 # noqa: W503 - and len(message.mentions) # noqa: W503 - - (1 if message.author in message.mentions else 0) # noqa: W503 - > massmention.value # noqa: W503 - ): - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Mass Mention", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - async def roleping(self, message: Message) -> None: - """Handle roleping events.""" - rolepings = await Roleping.find(q(guild=message.guild.id, active=True)) - - if not rolepings: - return - - # Get all role IDs involved with message - roles = [] - for mention in message.role_mentions: - roles.append(mention.id) - for mention in message.mentions: - for role in mention.roles: - roles.append(role.id) - - if not roles: - return - - # Get all roles that are rolepinged - roleping_ids = [r.role for r in rolepings] - - # Get roles in rolepings - role_in_rolepings = find_all(lambda x: x in roleping_ids, roles) - - # Check if the user has the role, so they are allowed to ping it - user_missing_role = any(x.id not in roleping_ids for x in message.author.roles) - - # Admins can ping whoever - user_is_admin = message.author.guild_permissions.ADMINISTRATOR - - # Check if user in a bypass list - user_has_bypass = False - for roleping in rolepings: - if message.author.id in roleping.bypass["users"]: - user_has_bypass = True - break - if any(role.id in roleping.bypass["roles"] for role in message.author.roles): - user_has_bypass = True - break - - if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Pinged a blocked role/user with a blocked role", - user=message.author.id, - ) - await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - - async def phishing(self, message: Message) -> None: - """Check if the message contains any known phishing domains.""" - for match in url.findall(message.content): - if match in self.phishing_domains: - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Phishing URL", - user=message.author.id, - ) - await w.commit() - fields = [EmbedField(name="Reason", value="Phishing URL", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.nick if message.author.nick else message.author.name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.name}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - await message.delete() - - async def malicious_url(self, message: Message) -> None: - """Check if the message contains any known phishing domains.""" - for match in url.findall(message.content): - async with ClientSession() as session: - resp = await session.get( - "https://spoopy.oceanlord.me/api/check_website", json={"website": match} - ) - if resp.status != 200: - break - data = await resp.json() - for item in data["processed"]["urls"].values(): - if not item["safe"]: - w = Warning( - active=True, - admin=self.user.id, - duration=24, - guild=message.guild.id, - reason="Unsafe URL", - user=message.author.id, - ) - await w.commit() - reasons = ", ".join(item["not_safe_reasons"]) - fields = [ - EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) - await message.channel.send(embed=embed) - await message.delete() - break - - @listen() - async def on_message(self, event: MessageCreate) -> None: - """Handle on_message event. Calls other event handlers.""" - message = event.message - if not isinstance(message.channel, DMChannel) and not message.author.bot: - await self.autoreact(message) - await self.massmention(message) - await self.roleping(message) - await self.autopurge(message) - await self.checks(message) - await self.malicious_url(message) From 51c3298b867354c3388ea33e2c1b61fc83dcf330 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:26:24 -0600 Subject: [PATCH 100/365] Create jarvis.utils.embeds for various embeds --- jarvis/client.py | 96 +++++------------------------------------- jarvis/utils/embeds.py | 21 +++++++++ 2 files changed, 31 insertions(+), 86 deletions(-) create mode 100644 jarvis/utils/embeds.py diff --git a/jarvis/client.py b/jarvis/client.py index 5e75498..80c1c9d 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -22,6 +22,8 @@ from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste +from jarvis.utils.embeds import warning_embed + DEFAULT_GUILD = 862402786116763668 DEFAULT_ERROR_CHANNEL = 943395824560394250 DEFAULT_SITE = "https://paste.zevs.me" @@ -213,25 +215,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Sent an invite link", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" # noqa: E501 - ) + embed = warning_embed(message.author, "Sent an invite link") await message.channel.send(embed=embed) async def massmention(self, message: Message) -> None: @@ -247,7 +231,7 @@ class Jarvis(Snake): massmention and massmention.value > 0 # noqa: W503 and len(message._mention_ids + message._mention_roles) # noqa: W503 - - (1 if message.author in message._mention_ids else 0) # noqa: W503 + - (1 if message.author.id in message._mention_ids else 0) # noqa: W503 > massmention.value # noqa: W503 ): w = Warning( @@ -259,26 +243,14 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [EmbedField(name="Reason", value="Mass Mention", inline=False)] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, "Mass Mention") await message.channel.send(embed=embed) async def roleping(self, message: Message) -> None: """Handle roleping events.""" if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: return - rolepings = Roleping.find(q(guild=message.guild.id, active=True)) + rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) # Get all role IDs involved with message roles = [] @@ -305,7 +277,7 @@ class Jarvis(Snake): # Check if user in a bypass list user_has_bypass = False - async for roleping in rolepings: + for roleping in rolepings: if message.author.id in roleping.bypass["users"]: user_has_bypass = True break @@ -323,25 +295,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField( - name="Reason", - value="Pinged a blocked role/user with a blocked role", - inline=False, - ) - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role") await message.channel.send(embed=embed) async def phishing(self, message: Message) -> None: @@ -357,22 +311,7 @@ class Jarvis(Snake): user=message.author.id, ) await w.commit() - fields = [ - EmbedField(name="Reason", value="Phishing URL", inline=False), - EmbedField(name="Message ID", value=str(message.id)), - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, "Phishing URL") await message.channel.send(embed=embed) await message.delete() return True @@ -400,22 +339,7 @@ class Jarvis(Snake): ) await w.commit() reasons = ", ".join(item["not_safe_reasons"]) - fields = [ - EmbedField(name="Reason", value=f"Unsafe URL: {reasons}", inline=False), - EmbedField(name="Message ID", valud=str(message.id)), - ] - embed = build_embed( - title="Warning", - description=f"{message.author.mention} has been warned", - fields=fields, - ) - embed.set_author( - name=message.author.display_name, - icon_url=message.author.display_avatar.url, - ) - embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" - ) + embed = warning_embed(message.author, reasons) await message.channel.send(embed=embed) await message.delete() return True diff --git a/jarvis/utils/embeds.py b/jarvis/utils/embeds.py new file mode 100644 index 0000000..31c9fbc --- /dev/null +++ b/jarvis/utils/embeds.py @@ -0,0 +1,21 @@ +"""JARVIS bot-specific embeds.""" +from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.user import Member +from jarvis_core.util import build_embed + + +def warning_embed(user: Member, reason: str) -> Embed: + """ + Generate a warning embed. + + Args: + user: User to warn + reason: Warning reason + """ + fields = [EmbedField(name="Reason", value=reason, inline=False)] + embed = build_embed( + title="Warning", description=f"{user.mention} has been warned", fields=fields + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + return embed From bf991aa6003d1e8cfed450f49eb51508c206b14e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:27:04 -0600 Subject: [PATCH 101/365] Fix add star. TODO: make sure starboard works --- jarvis/cogs/starboard.py | 104 +++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index a70d03a..8ae8da8 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -36,7 +36,7 @@ class StarboardCog(Scale): @slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _list(self, ctx: InteractionContext) -> None: - starboards = Starboard.objects(guild=ctx.guild.id) + starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) if starboards != []: message = "Available Starboards:\n" for s in starboards: @@ -71,16 +71,16 @@ class StarboardCog(Scale): await ctx.send(f"Starboard already exists at {channel.mention}.", ephemeral=True) return - count = Starboard.objects(guild=ctx.guild.id).count() + count = await Starboard.count_documents(q(guild=ctx.guild.id)) if count >= 25: await ctx.send("25 starboard limit reached", ephemeral=True) return - _ = Starboard( + await Starboard( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, - ).save() + ).commit() await ctx.send(f"Starboard created. Check it out at {channel.mention}.") @slash_command( @@ -94,29 +94,13 @@ class StarboardCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _delete(self, ctx: InteractionContext, channel: GuildText) -> None: - deleted = Starboard.objects(channel=channel.id, guild=ctx.guild.id).delete() - if deleted: - _ = Star.objects(starboard=channel.id).delete() + found = await Starboard.find_one(q(channel=channel.id, guild=ctx.guild.id)) + if found: + await found.delete() await ctx.send(f"Starboard deleted from {channel.mention}.") else: await ctx.send(f"Starboard not found in {channel.mention}.", ephemeral=True) - @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) - async def _star_message(self, ctx: InteractionContext) -> None: - await self._star_add._can_run(ctx) - await self._star_add.callback(ctx, message=str(ctx.target_id)) - - @slash_command(name="star", sub_cmd_name="add", description="Star a message") - @slash_option( - name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True - ) - @slash_option( - name="channel", - description="Channel that has the message, not required if used in same channel", - opt_type=OptionTypes.CHANNEL, - required=False, - ) - @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _star_add( self, ctx: InteractionContext, @@ -125,7 +109,7 @@ class StarboardCog(Scale): ) -> None: if not channel: channel = ctx.channel - starboards = Starboard.objects(guild=ctx.guild.id) + starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) if not starboards: await ctx.send("No starboards exist.", ephemeral=True) return @@ -135,7 +119,7 @@ class StarboardCog(Scale): if not isinstance(message, Message): if message.startswith("https://"): message = message.split("/")[-1] - message = await channel.get_message(int(message)) + message = await channel.fetch_message(int(message)) if not message: await ctx.send("Message not found", ephemeral=True) @@ -167,12 +151,14 @@ class StarboardCog(Scale): starboard = channel_list[int(com_ctx.context.values[0])] - exists = Star.objects( - message=message.id, - channel=message.channel.id, - guild=message.guild.id, - starboard=starboard.id, - ).first() + exists = await Star.find_one( + q( + message=message.id, + channel=channel.id, + guild=ctx.guild.id, + starboard=starboard.id, + ) + ) if exists: await ctx.send( @@ -181,7 +167,7 @@ class StarboardCog(Scale): ) return - count = Star.objects(guild=message.guild.id, starboard=starboard.id).count() + count = await Star.count_documents(q(guild=ctx.guild.id, starboard=starboard.id)) content = message.content attachments = message.attachments @@ -204,24 +190,24 @@ class StarboardCog(Scale): embed.set_author( name=message.author.display_name, url=message.jump_url, - icon_url=message.author.display_avatar.url, + icon_url=message.author.avatar.url, ) - embed.set_footer(text=message.guild.name + " | " + message.channel.name) + embed.set_footer(text=ctx.guild.name + " | " + channel.name) if image_url: embed.set_image(url=image_url) star = await starboard.send(embed=embed) - _ = Star( + await Star( index=count, message=message.id, - channel=message.channel.id, - guild=message.guild.id, + channel=channel.id, + guild=ctx.guild.id, starboard=starboard.id, admin=ctx.author.id, star=star.id, active=True, - ).save() + ).commit() components[0].components[0].disabled = True @@ -230,6 +216,27 @@ class StarboardCog(Scale): components=components, ) + @context_menu(name="Star Message", context_type=CommandTypes.MESSAGE) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _star_message(self, ctx: InteractionContext) -> None: + await self._star_add(ctx, message=str(ctx.target_id)) + + @slash_command(name="star", sub_cmd_name="add", description="Star a message") + @slash_option( + name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="channel", + description="Channel that has the message, not required if used in same channel", + opt_type=OptionTypes.CHANNEL, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _star_message_slash( + self, ctx: InteractionContext, message: str, channel: GuildText + ) -> None: + await self._star_add(ctx, message, channel) + @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True @@ -250,30 +257,33 @@ class StarboardCog(Scale): if not isinstance(starboard, GuildText): await ctx.send("Channel must be a GuildText channel", ephemeral=True) return + exists = await Starboard.find_one(q(channel=starboard.id, guild=ctx.guild.id)) if not exists: + # TODO: automagically create starboard await ctx.send( f"Starboard does not exist in {starboard.mention}. Please create it first", ephemeral=True, ) return - star = Star.objects( - starboard=starboard.id, - index=id, - guild=ctx.guild.id, - active=True, - ).first() + star = await Star.find_one( + q( + starboard=starboard.id, + index=id, + guild=ctx.guild.id, + active=True, + ) + ) if not star: await ctx.send(f"No star exists with id {id}", ephemeral=True) return - message = await starboard.get_message(star.star) + message = await starboard.fetch_message(star.star) if message: await message.delete() - star.active = False - star.save() + await star.delete() await ctx.send(f"Star {id} deleted from {starboard.mention}") From 3921c41f1588217068ee39684076cd33ffc13fa0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:27:48 -0600 Subject: [PATCH 102/365] Remove MongoEngine-related database stuff --- jarvis/db/models.py | 264 --------------------------------------- jarvis/utils/__init__.py | 1 - 2 files changed, 265 deletions(-) delete mode 100644 jarvis/db/models.py diff --git a/jarvis/db/models.py b/jarvis/db/models.py deleted file mode 100644 index 5c9b145..0000000 --- a/jarvis/db/models.py +++ /dev/null @@ -1,264 +0,0 @@ -"""J.A.R.V.I.S. database object for mongoengine.""" -from datetime import datetime - -from mongoengine import Document -from mongoengine.fields import ( - BooleanField, - DateTimeField, - DictField, - DynamicField, - IntField, - ListField, - LongField, - StringField, -) - - -class SnowflakeField(LongField): - """Snowflake LongField Override.""" - - pass - - -class Autopurge(Document): - """Autopurge database object.""" - - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - delay = IntField(min_value=1, max_value=300, default=30) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Autoreact(Document): - """Autoreact database object.""" - - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - reactions = ListField(field=StringField()) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Ban(Document): - """Ban database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - user = SnowflakeField(required=True) - username = StringField(required=True) - discrim = IntField(min_value=1, max_value=9999, required=True) - duration = IntField(min_value=1, max_value=744, required=False) - guild = SnowflakeField(required=True) - type = StringField(default="perm", max_length=4, required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Config(Document): - """Config database object.""" - - key = StringField(required=True) - value = DynamicField(required=True) - - meta = {"db_alias": "main"} - - -class Guess(Document): - """Guess database object.""" - - correct = BooleanField(default=False) - guess = StringField(max_length=800, required=True) - user = SnowflakeField(required=True) - - meta = {"db_alias": "ctc2"} - - -class Joke(Document): - """Joke database object.""" - - rid = StringField() - body = StringField() - title = StringField() - created_utc = DateTimeField() - over_18 = BooleanField() - score = IntField() - - meta = {"db_alias": "main"} - - -class Kick(Document): - """Kick database object.""" - - admin = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - user = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Lock(Document): - """Lock database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - duration = IntField(min_value=1, max_value=300, default=10) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Mute(Document): - """Mute database object.""" - - active = BooleanField(default=True) - user = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - duration = IntField(min_value=-1, max_value=300, default=10) - guild = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Purge(Document): - """Purge database object.""" - - admin = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - count = IntField(min_value=1, default=10) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Reminder(Document): - """Reminder database object.""" - - active = BooleanField(default=True) - user = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - message = StringField(max_length=100, required=True) - remind_at = DateTimeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - private = BooleanField(default=False) - - meta = {"db_alias": "main"} - - -class Rolegiver(Document): - """Rolegiver database object.""" - - guild = SnowflakeField(required=True) - roles = ListField(field=SnowflakeField()) - - meta = {"db_alias": "main"} - - -class Roleping(Document): - """Roleping database object.""" - - active = BooleanField(default=True) - role = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - bypass = DictField() - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Setting(Document): - """Setting database object.""" - - guild = SnowflakeField(required=True) - setting = StringField(required=True) - value = DynamicField() - - meta = {"db_alias": "main"} - - -class Star(Document): - """Star database object.""" - - active = BooleanField(default=True) - index = IntField(required=True) - message = SnowflakeField(required=True) - channel = SnowflakeField(required=True) - starboard = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - star = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Starboard(Document): - """Starboard database object.""" - - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Twitter(Document): - """Twitter Follow object.""" - - active = BooleanField(default=True) - twitter_id = IntField(required=True) - handle = StringField(required=True) - channel = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - last_tweet = SnowflakeField(required=True) - retweets = BooleanField(default=True) - admin = SnowflakeField(required=True) - created_at = DateTimeField(default=datetime.utcnow) - last_sync = DateTimeField() - - meta = {"db_alias": "main"} - - -class Unban(Document): - """Unban database object.""" - - user = SnowflakeField(required=True) - username = StringField(required=True) - discrim = IntField(min_value=1, max_value=9999, required=True) - guild = SnowflakeField(required=True) - admin = SnowflakeField(required=True) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} - - -class Warning(Document): - """Warning database object.""" - - active = BooleanField(default=True) - admin = SnowflakeField(required=True) - user = SnowflakeField(required=True) - guild = SnowflakeField(required=True) - duration = IntField(min_value=1, max_value=120, default=24) - reason = StringField(max_length=100, required=True) - created_at = DateTimeField(default=datetime.utcnow) - - meta = {"db_alias": "main"} diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index ad04b08..6b0d89a 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -8,7 +8,6 @@ from dis_snek.models.discord.guild import AuditLogEntry from dis_snek.models.discord.user import Member import jarvis.cogs -import jarvis.db from jarvis.config import get_config __all__ = ["cachecog", "permissions"] From a9d736c7a81a193d8a389c4549b267caacf162c5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:29:04 -0600 Subject: [PATCH 103/365] Fix admin-related commands. TODO: Settings, Lock, Lockdown, Mute --- jarvis/cogs/admin/ban.py | 8 --- jarvis/cogs/admin/purge.py | 10 ++- jarvis/cogs/admin/roleping.py | 122 +++++++++++++++++----------------- jarvis/cogs/admin/warning.py | 54 ++++++--------- 4 files changed, 87 insertions(+), 107 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 4db8461..f4742cb 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -302,14 +302,6 @@ class BanCog(Scale): @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: active = bool(active) - exists = self.check_cache(ctx, btype=btype, active=active) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return types = [0, "perm", "temp", "soft"] search = {"guild": ctx.guild.id} if active: diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index a5a73c6..91d7caa 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -37,13 +37,12 @@ class PurgeCog(Scale): async for message in ctx.channel.history(limit=amount + 1): messages.append(message) await ctx.channel.delete_messages(messages, reason=f"Purge by {ctx.author.username}") - p = Purge( + await Purge( channel=ctx.channel.id, guild=ctx.guild.id, admin=ctx.author.id, count=amount, - ) - await p.commit() + ).commit() @slash_command( name="autopurge", sub_cmd_name="add", sub_cmd_description="Automatically purge messages" @@ -79,13 +78,12 @@ class PurgeCog(Scale): await ctx.send("Autopurge already exists.", ephemeral=True) return - p = Autopurge( + await Autopurge( guild=ctx.guild.id, channel=channel.id, admin=ctx.author.id, delay=delay, - ) - await p.commit() + ).commit() await ctx.send(f"Autopurge set up on {channel.mention}, delay is {delay} seconds") diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 57e40dc..a9bf020 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -32,13 +32,13 @@ class RolepingCog(Scale): await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return - _ = Roleping( + _ = await Roleping( role=role.id, guild=ctx.guild.id, admin=ctx.author.id, active=True, bypass={"roles": [], "users": []}, - ).save() + ).commit() await ctx.send(f"Role `{role.name}` added to roleping.") @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") @@ -47,29 +47,29 @@ class RolepingCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_remove(self, ctx: InteractionContext, role: Role) -> None: - roleping = Roleping.objects(guild=ctx.guild.id, role=role.id) + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: await ctx.send("Roleping does not exist", ephemeral=True) return - roleping.delete() + await roleping.delete() await ctx.send(f"Role `{role.name}` removed from roleping.") @slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles") async def _roleping_list(self, ctx: InteractionContext) -> None: - rolepings = Roleping.objects(guild=ctx.guild.id) + rolepings = await Roleping.find(q(guild=ctx.guild.id)).to_list(None) if not rolepings: await ctx.send("No rolepings configured", ephemeral=True) return embeds = [] for roleping in rolepings: - role = await ctx.guild.get_role(roleping.role) + role = await ctx.guild.fetch_role(roleping.role) broles = find_all(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles) bypass_roles = [r.mention or "||`[redacted]`||" for r in broles] bypass_users = [ - await ctx.guild.get_member(u).mention or "||`[redacted]`||" + (await ctx.guild.fetch_member(u)).mention or "||`[redacted]`||" for u in roleping.bypass["users"] ] bypass_roles = bypass_roles or ["None"] @@ -84,24 +84,26 @@ class RolepingCog(Scale): value=roleping.created_at.strftime("%a, %b %d, %Y %I:%M %p"), inline=False, ), - EmbedField(name="Active", value=str(roleping.active)), + # EmbedField(name="Active", value=str(roleping.active), inline=True), EmbedField( name="Bypass Users", value="\n".join(bypass_users), + inline=True, ), EmbedField( name="Bypass Roles", value="\n".join(bypass_roles), + inline=True, ), ], ) - admin = await ctx.guild.get_member(roleping.admin) + admin = await ctx.guild.fetch_member(roleping.admin) if not admin: admin = self.bot.user embed.set_author(name=admin.display_name, icon_url=admin.display_avatar.url) - embed.set_footer(text=f"{admin.name}#{admin.discriminator} | {admin.id}") + embed.set_footer(text=f"{admin.username}#{admin.discriminator} | {admin.id}") embeds.append(embed) @@ -117,21 +119,23 @@ class RolepingCog(Scale): sub_cmd_name="user", sub_cmd_description="Add a user as a bypass to a roleping", ) - @slash_option(name="user", description="User to add", opt_type=OptionTypes.USER, required=True) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="User to add", opt_type=OptionTypes.USER, required=True + ) + @slash_option( + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_bypass_user( - self, ctx: InteractionContext, user: Member, rping: Role + self, ctx: InteractionContext, bypass: Member, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if user.id in roleping.bypass["users"]: - await ctx.send(f"{user.mention} already in bypass", ephemeral=True) + if bypass.id in roleping.bypass["users"]: + await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["users"]) == 10: @@ -141,18 +145,18 @@ class RolepingCog(Scale): ) return - matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], user.roles)) + matching_role = list(filter(lambda x: x.id in roleping.bypass["roles"], bypass.roles)) if matching_role: await ctx.send( - f"{user.mention} already has bypass via {matching_role[0].mention}", + f"{bypass.mention} already has bypass via {matching_role[0].mention}", ephemeral=True, ) return - roleping.bypass["users"].append(user.id) - roleping.save() - await ctx.send(f"{user.display_name} user bypass added for `{rping.name}`") + roleping.bypass["users"].append(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass added for `{role.name}`") @slash_command( name="roleping", @@ -160,19 +164,23 @@ class RolepingCog(Scale): sub_cmd_name="role", description="Add a role as a bypass to roleping", ) - @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="Role to add", opt_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _roleping_bypass_role(self, ctx: InteractionContext, role: Role, rping: Role) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + async def _roleping_bypass_role( + self, ctx: InteractionContext, bypass: Role, role: Role + ) -> None: + roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", ephemeral=True) + if bypass.id in roleping.bypass["roles"]: + await ctx.send(f"{bypass.mention} already in bypass", ephemeral=True) return if len(roleping.bypass["roles"]) == 10: @@ -183,9 +191,9 @@ class RolepingCog(Scale): ) return - roleping.bypass["roles"].append(role.id) - roleping.save() - await ctx.send(f"{role.name} role bypass added for `{rping.name}`") + roleping.bypass["roles"].append(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.name} role bypass added for `{role.name}`") @slash_command( name="roleping", @@ -196,27 +204,27 @@ class RolepingCog(Scale): sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="user", description="User to remove", opt_type=OptionTypes.USER, required=True + name="bypass", description="User to remove", opt_type=OptionTypes.USER, required=True ) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_restore_user( - self, ctx: InteractionContext, user: Member, rping: Role + self, ctx: InteractionContext, bypass: Member, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if user.id not in roleping.bypass["users"]: - await ctx.send(f"{user.mention} not in bypass", ephemeral=True) + if bypass.id not in roleping.bypass.users: + await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True) return - roleping.bypass["users"].delete(user.id) - roleping.save() - await ctx.send(f"{user.display_name} user bypass removed for `{rping.name}`") + roleping.bypass.users.remove(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") @slash_command( name="roleping", @@ -225,32 +233,24 @@ class RolepingCog(Scale): description="Remove a bypass from a roleping (restoring it)", ) @slash_option( - name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True + name="bypass", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) @slash_option( - name="rping", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True + name="role", description="Rolepinged role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _roleping_restore_role( - self, ctx: InteractionContext, role: Role, rping: Role + self, ctx: InteractionContext, bypass: Role, role: Role ) -> None: - roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=rping.id)) + roleping: Roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: - await ctx.send(f"Roleping not configured for {rping.mention}", ephemeral=True) + await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) return - if role.id in roleping.bypass["roles"]: - await ctx.send(f"{role.mention} already in bypass", ephemeral=True) + if bypass.id not in roleping.bypass.roles: + await ctx.send(f"{bypass.mention} not in bypass", ephemeral=True) return - if len(roleping.bypass["roles"]) == 10: - await ctx.send( - "Already have 10 roles in bypass. " - "Please consider consolidating roles for roleping bypass", - ephemeral=True, - ) - return - - roleping.bypass["roles"].append(role.id) - roleping.save() - await ctx.send(f"{role.name} role bypass added for `{rping.name}`") + roleping.bypass.roles.remove(bypass.id) + await roleping.commit() + await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 7145cca..7e60987 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,6 +1,8 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions, Scale +from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( OptionTypes, @@ -9,20 +11,16 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db.models import Warning -from jarvis.db.models import Warning from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog -from jarvis.utils.field import Field +from jarvis.utils.embeds import warning_embed from jarvis.utils.permissions import admin_or_permissions -class WarningCog(CacheCog): +class WarningCog(Scale): """J.A.R.V.I.S. WarningCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - @slash_command(name="warn", description="Warn a user") @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -51,23 +49,15 @@ class WarningCog(CacheCog): await ctx.send("Duration must be < 5 days", ephemeral=True) return await ctx.defer() - _ = Warning( + await Warning( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, active=True, - ).save() - fields = [Field("Reason", reason, False)] - embed = build_embed( - title="Warning", - description=f"{user.mention} has been warned", - fields=fields, - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_footer(text=f"{user.name}#{user.discriminator} | {user.id}") - + ).commit() + embed = warning_embed(user, reason) await ctx.send(embed=embed) @slash_command(name="warnings", description="Get count of user warnings") @@ -86,17 +76,19 @@ class WarningCog(CacheCog): async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: active = bool(active) - warnings = Warning.objects( - user=user.id, - guild=ctx.guild.id, - ).order_by("-created_at") - active_warns = Warning.objects(user=user.id, guild=ctx.guild.id, active=True).order_by( - "-created_at" + warnings = ( + await Warning.find( + user=user.id, + guild=ctx.guild.id, + ) + .sort("created_at", -1) + .to_list(None) ) + active_warns = get_all(warnings, active=True) pages = [] if active: - if active_warns.count() == 0: + if len(active_warns) == 0: embed = build_embed( title="Warnings", description=f"{warnings.count()} total | 0 currently active", @@ -113,7 +105,7 @@ class WarningCog(CacheCog): if admin: admin_name = f"{admin.username}#{admin.discriminator}" fields.append( - Field( + EmbedField( name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"), value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b", inline=False, @@ -123,7 +115,7 @@ class WarningCog(CacheCog): embed = build_embed( title="Warnings", description=( - f"{warnings.count()} total | {active_warns.count()} currently active" + f"{len(warnings)} total | {len(active_warns)} currently active" ), fields=fields[i : i + 5], ) @@ -140,7 +132,7 @@ class WarningCog(CacheCog): title = "[A] " if warn.active else "[I] " title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC") fields.append( - Field( + EmbedField( name=title, value=warn.reason + "\n\u200b", inline=False, @@ -149,9 +141,7 @@ class WarningCog(CacheCog): for i in range(0, len(fields), 5): embed = build_embed( title="Warnings", - description=( - f"{warnings.count()} total | {active_warns.count()} currently active" - ), + description=(f"{len(warnings)} total | {len(active_warns)} currently active"), fields=fields[i : i + 5], ) embed.set_author( From 81f1bc5cd851b3d473585fc8d56296c6ed9f3bd2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:29:30 -0600 Subject: [PATCH 104/365] Various small unfinished changes --- jarvis/cogs/admin/mute.py | 242 ++++++++++++++++++------------------- jarvis/cogs/settings.py | 22 ++-- poetry.lock | 243 ++++++++++++++++++++++++-------------- 3 files changed, 289 insertions(+), 218 deletions(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 4be1910..5ac5ec5 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,122 +1,122 @@ """J.A.R.V.I.S. MuteCog.""" -# from datetime import datetime -# -# from dis_snek import InteractionContext, Permissions, Scale, Snake -# from dis_snek.models.discord.embed import EmbedField -# from dis_snek.models.discord.user import Member -# from dis_snek.models.snek.application_commands import ( -# OptionTypes, -# SlashCommandChoice, -# slash_command, -# slash_option, -# ) -# from dis_snek.models.snek.command import check -# -# from jarvis.db.models import Mute -# from jarvis.utils import build_embed -# from jarvis.utils.permissions import admin_or_permissions -# -# -# class MuteCog(Scale): -# """J.A.R.V.I.S. MuteCog.""" -# -# def __init__(self, bot: Snake): -# self.bot = bot -# -# @slash_command(name="mute", description="Mute a user") -# @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) -# @slash_option( -# name="reason", -# description="Reason for mute", -# opt_type=OptionTypes.STRING, -# required=True, -# ) -# @slash_option( -# name="time", -# description="Duration of mute, default 1", -# opt_type=OptionTypes.INTEGER, -# required=False, -# ) -# @slash_option( -# name="scale", -# description="Time scale, default Hour(s)", -# opt_type=OptionTypes.INTEGER, -# required=False, -# choices=[ -# SlashCommandChoice(name="Minute(s)", value=1), -# SlashCommandChoice(name="Hour(s)", value=60), -# SlashCommandChoice(name="Day(s)", value=3600), -# SlashCommandChoice(name="Week(s)", value=604800), -# ], -# ) -# @check( -# admin_or_permissions( -# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS -# ) -# ) -# async def _timeout( -# self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 -# ) -> None: -# if user == ctx.author: -# await ctx.send("You cannot mute yourself.", ephemeral=True) -# return -# if user == self.bot.user: -# await ctx.send("I'm afraid I can't let you do that", ephemeral=True) -# return -# if len(reason) > 100: -# await ctx.send("Reason must be < 100 characters", ephemeral=True) -# return -# -# # Max 4 weeks (2419200 seconds) per API -# duration = time * scale -# if duration > 2419200: -# await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) -# return -# -# await user.timeout(communication_disabled_until=duration, reason=reason) -# m = Mute( -# user=user.id, -# reason=reason, -# admin=ctx.author.id, -# guild=ctx.guild.id, -# duration=duration, -# active=True, -# ) -# await m.commit() -# -# embed = build_embed( -# title="User Muted", -# description=f"{user.mention} has been muted", -# fields=[EmbedField(name="Reason", value=reason)], -# ) -# embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) -# embed.set_thumbnail(url=user.display_avatar.url) -# embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") -# await ctx.send(embed=embed) -# -# @slash_command(name="unmute", description="Unmute a user") -# @slash_option( -# name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True -# ) -# @check( -# admin_or_permissions( -# Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS -# ) -# ) -# async def _unmute(self, ctx: InteractionContext, user: Member) -> None: -# if ( -# not user.communication_disabled_until -# or user.communication_disabled_until < datetime.now() # noqa: W503 -# ): -# await ctx.send("User is not muted", ephemeral=True) -# return -# -# embed = build_embed( -# title="User Unmuted", -# description=f"{user.mention} has been unmuted", -# fields=[], -# ) -# embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) -# embed.set_thumbnail(url=user.display_avatar.url) -# embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") -# await ctx.send(embed=embed) +from datetime import datetime + +from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.user import Member +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommandChoice, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db.models import Mute + +from jarvis.utils import build_embed +from jarvis.utils.permissions import admin_or_permissions + + +class MuteCog(Scale): + """J.A.R.V.I.S. MuteCog.""" + + def __init__(self, bot: Snake): + self.bot = bot + + @slash_command(name="mute", description="Mute a user") + @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="reason", + description="Reason for mute", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="time", + description="Duration of mute, default 1", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @slash_option( + name="scale", + description="Time scale, default Hour(s)", + opt_type=OptionTypes.INTEGER, + required=False, + choices=[ + SlashCommandChoice(name="Minute(s)", value=1), + SlashCommandChoice(name="Hour(s)", value=60), + SlashCommandChoice(name="Day(s)", value=3600), + SlashCommandChoice(name="Week(s)", value=604800), + ], + ) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _timeout( + self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 + ) -> None: + if user == ctx.author: + await ctx.send("You cannot mute yourself.", ephemeral=True) + return + if user == self.bot.user: + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) + return + if len(reason) > 100: + await ctx.send("Reason must be < 100 characters", ephemeral=True) + return + + # Max 4 weeks (2419200 seconds) per API + duration = time * scale + if duration > 2419200: + await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) + return + + await user.timeout(communication_disabled_until=duration, reason=reason) + m = Mute( + user=user.id, + reason=reason, + admin=ctx.author.id, + guild=ctx.guild.id, + duration=duration, + active=True, + ) + await m.commit() + + embed = build_embed( + title="User Muted", + description=f"{user.mention} has been muted", + fields=[EmbedField(name="Reason", value=reason)], + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_thumbnail(url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + await ctx.send(embed=embed) + + @slash_command(name="unmute", description="Unmute a user") + @slash_option( + name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True + ) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _unmute(self, ctx: InteractionContext, user: Member) -> None: + if ( + not user.communication_disabled_until + or user.communication_disabled_until < datetime.now() # noqa: W503 + ): + await ctx.send("User is not muted", ephemeral=True) + return + + embed = build_embed( + title="User Unmuted", + description=f"{user.mention} has been unmuted", + fields=[], + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_thumbnail(url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + await ctx.send(embed=embed) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index d761bc1..c197573 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -63,24 +63,24 @@ class SettingsCog(commands.Cog): @cog_ext.cog_subcommand( base="settings", subcommand_group="set", - name="userlog", - description="Set userlog channel", + name="activitylog", + description="Set activitylog channel", choices=[ create_option( name="channel", - description="Userlog channel", + description="Activitylog channel", opt_type=7, required=True, ) ], ) @check(admin_or_permissions(manage_guild=True)) - async def _set_userlog(self, ctx: SlashContext, channel: TextChannel) -> None: + async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None: if not isinstance(channel, TextChannel): await ctx.send("Channel must be a TextChannel", ephemeral=True) return - self.update_settings("userlog", channel.id, ctx.guild.id) - await ctx.send(f"Settings applied. New userlog channel is {channel.mention}") + self.update_settings("activitylog", channel.id, ctx.guild.id) + await ctx.send(f"Settings applied. New activitylog channel is {channel.mention}") @cog_ext.cog_subcommand( base="settings", @@ -176,12 +176,12 @@ class SettingsCog(commands.Cog): @cog_ext.cog_subcommand( base="settings", subcommand_group="unset", - name="userlog", - description="Unset userlog channel", + name="activitylog", + description="Unset activitylog channel", ) @check(admin_or_permissions(manage_guild=True)) - async def _unset_userlog(self, ctx: SlashContext) -> None: - self.delete_settings("userlog", ctx.guild.id) + async def _unset_activitylog(self, ctx: SlashContext) -> None: + self.delete_settings("activitylog", ctx.guild.id) await ctx.send("Setting removed.") @cog_ext.cog_subcommand( @@ -234,7 +234,7 @@ class SettingsCog(commands.Cog): value = value.mention else: value = "||`[redacted]`||" - elif setting.setting in ["userlog", "modlog"]: + elif setting.setting in ["activitylog", "modlog"]: value = find(lambda x: x.id == value, ctx.guild.text_channels) if value: value = value.mention diff --git a/poetry.lock b/poetry.lock index 2d8bfcf..cf974fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -117,7 +117,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -136,7 +136,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dis-snek" -version = "6.0.0" +version = "7.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -145,8 +145,20 @@ python-versions = ">=3.10" [package.dependencies] aiohttp = "*" attrs = "*" +discord-typings = "*" tomli = "*" +[[package]] +name = "discord-typings" +version = "0.3.1" +description = "Maintained typings of payloads that Discord sends" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing_extensions = ">=4,<5" + [[package]] name = "flake8" version = "4.0.1" @@ -181,7 +193,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "main" optional = false @@ -214,7 +226,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.6.0" +version = "0.6.1" description = "" category = "main" optional = false @@ -232,7 +244,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "8e3276a81575927e4484dfaa5c3e0e894e3c4707" +resolved_reference = "52a3d568030a79db8ad5ddf65c26216913598bf5" [[package]] name = "jedi" @@ -259,16 +271,19 @@ python-versions = ">=3.6" [[package]] name = "marshmallow" -version = "3.14.1" +version = "3.15.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[package.dependencies] +packaging = "*" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] -docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] -lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -322,7 +337,7 @@ python-versions = "*" [[package]] name = "numpy" -version = "1.22.2" +version = "1.22.3" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -343,7 +358,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "opencv-python" -version = "4.5.5.62" +version = "4.5.5.64" description = "Wrapper package for OpenCV python bindings." category = "main" optional = false @@ -359,12 +374,23 @@ numpy = [ [[package]] name = "orjson" -version = "3.6.6" +version = "3.6.7" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + [[package]] name = "parso" version = "0.8.3" @@ -418,7 +444,7 @@ python-versions = ">=3.7" [[package]] name = "platformdirs" -version = "2.5.0" +version = "2.5.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -523,9 +549,20 @@ srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] zstd = ["zstandard"] +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + [[package]] name = "python-gitlab" -version = "3.1.1" +version = "3.2.0" description = "Interact with GitLab API" category = "main" optional = false @@ -643,7 +680,7 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rope" -version = "0.22.0" +version = "0.23.0" description = "a python refactoring library..." category = "dev" optional = false @@ -686,22 +723,31 @@ python-versions = ">=3.7" [[package]] name = "tweepy" -version = "4.5.0" +version = "4.7.0" description = "Twitter library for Python" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] +oauthlib = ">=3.2.0,<4" requests = ">=2.27.0,<3" -requests-oauthlib = ">=1.0.0,<2" +requests-oauthlib = ">=1.2.0,<2" [package.extras] -async = ["aiohttp (>=3.7.3,<4)", "oauthlib (>=3.1.0,<4)"] +async = ["aiohttp (>=3.7.3,<4)"] dev = ["coveralls (>=2.1.0)", "tox (>=3.14.0)"] socks = ["requests[socks] (>=2.27.0,<3)"] test = ["vcrpy (>=1.10.3)"] +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "ujson" version = "5.1.0" @@ -910,16 +956,20 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dis-snek = [ - {file = "dis-snek-6.0.0.tar.gz", hash = "sha256:3abe2af832bd87adced01ebc697e418b25871a4158ac8ba2e202d8fbb2921f44"}, - {file = "dis_snek-6.0.0-py3-none-any.whl", hash = "sha256:106cb08b0cc982c59db31be01ee529e4d7273835de7f418562d8e6b24d7f965d"}, + {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, + {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, +] +discord-typings = [ + {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, + {file = "discord_typings-0.3.1-py3-none-any.whl", hash = "sha256:65890c467751daa025dcef15683c32160f07427baf83380cfdf11d84ceec980a"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -991,8 +1041,8 @@ gitdb = [ {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -1047,8 +1097,8 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] marshmallow = [ - {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, - {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, + {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, + {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1128,64 +1178,77 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] numpy = [ - {file = "numpy-1.22.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:515a8b6edbb904594685da6e176ac9fbea8f73a5ebae947281de6613e27f1956"}, - {file = "numpy-1.22.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76a4f9bce0278becc2da7da3b8ef854bed41a991f4226911a24a9711baad672c"}, - {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:168259b1b184aa83a514f307352c25c56af111c269ffc109d9704e81f72e764b"}, - {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3556c5550de40027d3121ebbb170f61bbe19eb639c7ad0c7b482cd9b560cd23b"}, - {file = "numpy-1.22.2-cp310-cp310-win_amd64.whl", hash = "sha256:aafa46b5a39a27aca566198d3312fb3bde95ce9677085efd02c86f7ef6be4ec7"}, - {file = "numpy-1.22.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:55535c7c2f61e2b2fc817c5cbe1af7cb907c7f011e46ae0a52caa4be1f19afe2"}, - {file = "numpy-1.22.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:60cb8e5933193a3cc2912ee29ca331e9c15b2da034f76159b7abc520b3d1233a"}, - {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b536b6840e84c1c6a410f3a5aa727821e6108f3454d81a5cd5900999ef04f89"}, - {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2638389562bda1635b564490d76713695ff497242a83d9b684d27bb4a6cc9d7a"}, - {file = "numpy-1.22.2-cp38-cp38-win32.whl", hash = "sha256:6767ad399e9327bfdbaa40871be4254d1995f4a3ca3806127f10cec778bd9896"}, - {file = "numpy-1.22.2-cp38-cp38-win_amd64.whl", hash = "sha256:03ae5850619abb34a879d5f2d4bb4dcd025d6d8fb72f5e461dae84edccfe129f"}, - {file = "numpy-1.22.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d76a26c5118c4d96e264acc9e3242d72e1a2b92e739807b3b69d8d47684b6677"}, - {file = "numpy-1.22.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15efb7b93806d438e3bc590ca8ef2f953b0ce4f86f337ef4559d31ec6cf9d7dd"}, - {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badca914580eb46385e7f7e4e426fea6de0a37b9e06bec252e481ae7ec287082"}, - {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dd11d9f13ea1be17bac39c1942f527cbf7065f94953cf62dfe805653da2f8f"}, - {file = "numpy-1.22.2-cp39-cp39-win32.whl", hash = "sha256:8cf33634b60c9cef346663a222d9841d3bbbc0a2f00221d6bcfd0d993d5543f6"}, - {file = "numpy-1.22.2-cp39-cp39-win_amd64.whl", hash = "sha256:59153979d60f5bfe9e4c00e401e24dfe0469ef8da6d68247439d3278f30a180f"}, - {file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"}, - {file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"}, + {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, + {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"}, + {file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"}, + {file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"}, + {file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"}, + {file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"}, + {file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"}, + {file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"}, + {file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"}, + {file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"}, ] oauthlib = [ {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, ] opencv-python = [ - {file = "opencv-python-4.5.5.62.tar.gz", hash = "sha256:3efe232b32d5e1327e7c82bc6d61230737821c5190ce5c783e64a1bc8d514e18"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:2601388def0d6b957cc30dd88f8ff74a5651ae6940dd9e488241608cfa2b15c7"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71fdc49df412b102d97f14927321309043c79c4a3582cce1dc803370ff9c39c0"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:130cc75d56b29aa3c5de8b6ac438242dd2574ba6eaa8bccdffdcfd6b78632f7f"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-win32.whl", hash = "sha256:3a75c7ad45b032eea0c72e389aac6dd435f5c87e87f60237095c083400bc23aa"}, - {file = "opencv_python-4.5.5.62-cp36-abi3-win_amd64.whl", hash = "sha256:c463d2276d8662b972d20ca9644702188507de200ca5405b89e1fe71c5c99989"}, - {file = "opencv_python-4.5.5.62-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:ac92e743e22681f30001942d78512c1e39bce53dbffc504e5645fdc45c0f2c47"}, + {file = "opencv-python-4.5.5.64.tar.gz", hash = "sha256:f65de0446a330c3b773cd04ba10345d8ce1b15dcac3f49770204e37602d0b3f7"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-macosx_10_15_x86_64.whl", hash = "sha256:a512a0c59b6fec0fac3844b2f47d6ecb1a9d18d235e6c5491ce8dbbe0663eae8"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6138b6903910e384067d001763d40f97656875487381aed32993b076f44375"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b293ced62f4360d9f11cf72ae7e9df95320ff7bf5b834d87546f844e838c0c35"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win32.whl", hash = "sha256:6247e584813c00c3b9ed69a795da40d2c153dc923d0182e957e1c2f00a554ac2"}, + {file = "opencv_python-4.5.5.64-cp36-abi3-win_amd64.whl", hash = "sha256:408d5332550287aa797fd06bef47b2dfed163c6787668cc82ef9123a9484b56a"}, + {file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"}, ] orjson = [ - {file = "orjson-3.6.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e4a7cad6c63306318453980d302c7c0b74c0cc290dd1f433bbd7d31a5af90cf1"}, - {file = "orjson-3.6.6-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e533941dca4a0530a876de32e54bf2fd3269cdec3751aebde7bfb5b5eba98e74"}, - {file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:9adf63be386eaa34278967512b83ff8fc4bed036a246391ae236f68d23c47452"}, - {file = "orjson-3.6.6-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:3b636753ae34d4619b11ea7d664a2f1e87e55e9738e5123e12bcce22acae9d13"}, - {file = "orjson-3.6.6-cp310-none-win_amd64.whl", hash = "sha256:78a10295ed048fd916c6584d6d27c232eae805a43e7c14be56e3745f784f0eb6"}, - {file = "orjson-3.6.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:82b4f9fb2af7799b52932a62eac484083f930d5519560d6f64b24d66a368d03f"}, - {file = "orjson-3.6.6-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a0033d07309cc7d8b8c4bc5d42f0dd4422b53ceb91dee9f4086bb2afa70b7772"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b321f99473116ab7c7c028377372f7b4adba4029aaca19cd567e83898f55579"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:b9c98ed94f1688cc11b5c61b8eea39d854a1a2f09f71d8a5af005461b14994ed"}, - {file = "orjson-3.6.6-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:00b333a41392bd07a8603c42670547dbedf9b291485d773f90c6470eff435608"}, - {file = "orjson-3.6.6-cp37-none-win_amd64.whl", hash = "sha256:8d4fd3bdee65a81f2b79c50937d4b3c054e1e6bfa3fc72ed018a97c0c7c3d521"}, - {file = "orjson-3.6.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:954c9f8547247cd7a8c91094ff39c9fe314b5eaeaec90b7bfb7384a4108f416f"}, - {file = "orjson-3.6.6-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:74e5aed657ed0b91ef05d44d6a26d3e3e12ce4d2d71f75df41a477b05878c4a9"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4008a5130e6e9c33abaa95e939e0e755175da10745740aa6968461b2f16830e2"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:012761d5f3d186deb4f6238f15e9ea7c1aac6deebc8f5b741ba3b4fafe017460"}, - {file = "orjson-3.6.6-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:b464546718a940b48d095a98df4c04808bfa6c8706fe751fc3f9390bc2f82643"}, - {file = "orjson-3.6.6-cp38-none-win_amd64.whl", hash = "sha256:f10a800f4e5a4aab52076d4628e9e4dab9370bdd9d8ea254ebfde846b653ab25"}, - {file = "orjson-3.6.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:8010d2610cfab721725ef14d578c7071e946bbdae63322d8f7b49061cf3fde8d"}, - {file = "orjson-3.6.6-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8dca67a4855e1e0f9a2ea0386e8db892708522e1171dc0ddf456932288fbae63"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af065d60523139b99bd35b839c7a2d8c5da55df8a8c4402d2eb6cdc07fa7a624"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:fa1f389cc9f766ae0cf7ba3533d5089836b01a5ccb3f8d904297f1fcf3d9dc34"}, - {file = "orjson-3.6.6-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:ec1221ad78f94d27b162a1d35672b62ef86f27f0e4c2b65051edb480cc86b286"}, - {file = "orjson-3.6.6-cp39-none-win_amd64.whl", hash = "sha256:afed2af55eeda1de6b3f1cbc93431981b19d380fcc04f6ed86e74c1913070304"}, - {file = "orjson-3.6.6.tar.gz", hash = "sha256:55dd988400fa7fbe0e31407c683f5aaab013b5bd967167b8fe058186773c4d6c"}, + {file = "orjson-3.6.7-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93188a9d6eb566419ad48befa202dfe7cd7a161756444b99c4ec77faea9352a4"}, + {file = "orjson-3.6.7-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82515226ecb77689a029061552b5df1802b75d861780c401e96ca6bc8495f775"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3af57ffab7848aaec6ba6b9e9b41331250b57bf696f9d502bacdc71a0ebab0ba"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:a7297504d1142e7efa236ffc53f056d73934a993a08646dbcee89fc4308a8fcf"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:5a50cde0dbbde255ce751fd1bca39d00ecd878ba0903c0480961b31984f2fab7"}, + {file = "orjson-3.6.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d21f9a2d1c30e58070f93988db4cad154b9009fafbde238b52c1c760e3607fbe"}, + {file = "orjson-3.6.7-cp310-none-win_amd64.whl", hash = "sha256:e152464c4606b49398afd911777decebcf9749cc8810c5b4199039e1afb0991e"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:0a65f3c403f38b0117c6dd8e76e85a7bd51fcd92f06c5598dfeddbc44697d3e5"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c47cfca18e41f7f37b08ff3e7abf5ada2d0f27b5ade934f05be5fc5bb956e9d"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63185af814c243fad7a72441e5f98120c9ecddf2675befa486d669fb65539e9b"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2da6fde42182b80b40df2e6ab855c55090ebfa3fcc21c182b7ad1762b61d55c"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:48c5831ec388b4e2682d4ff56d6bfa4a2ef76c963f5e75f4ff4785f9cf338a80"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:913fac5d594ccabf5e8fbac15b9b3bb9c576d537d49eeec9f664e7a64dde4c4b"}, + {file = "orjson-3.6.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:58f244775f20476e5851e7546df109f75160a5178d44257d437ba6d7e562bfe8"}, + {file = "orjson-3.6.7-cp37-none-win_amd64.whl", hash = "sha256:2d5f45c6b85e5f14646df2d32ecd7ff20fcccc71c0ea1155f4d3df8c5299bbb7"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:612d242493afeeb2068bc72ff2544aa3b1e627578fcf92edee9daebb5893ffea"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:539cdc5067db38db27985e257772d073cd2eb9462d0a41bde96da4e4e60bd99b"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d103b721bbc4f5703f62b3882e638c0b65fcdd48622531c7ffd45047ef8e87c"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb10a20f80e95102dd35dfbc3a22531661b44a09b55236b012a446955846b023"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:bb68d0da349cf8a68971a48ad179434f75256159fe8b0715275d9b49fa23b7a3"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:4a2c7d0a236aaeab7f69c17b7ab4c078874e817da1bfbb9827cb8c73058b3050"}, + {file = "orjson-3.6.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3be045ca3b96119f592904cf34b962969ce97bd7843cbfca084009f6c8d2f268"}, + {file = "orjson-3.6.7-cp38-none-win_amd64.whl", hash = "sha256:bd765c06c359d8a814b90f948538f957fa8a1f55ad1aaffcdc5771996aaea061"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7dd9e1e46c0776eee9e0649e3ae9584ea368d96851bcaeba18e217fa5d755283"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c4b4f20a1e3df7e7c83717aff0ef4ab69e42ce2fb1f5234682f618153c458406"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7107a5673fd0b05adbb58bf71c1578fc84d662d29c096eb6d998982c8635c221"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08b6940dd9a98ccf09785890112a0f81eadb4f35b51b9a80736d1725437e22c"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:f5d1648e5a9d1070f3628a69a7c6c17634dbb0caf22f2085eca6910f7427bf1f"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e6201494e8dff2ce7fd21da4e3f6dfca1a3fed38f9dcefc972f552f6596a7621"}, + {file = "orjson-3.6.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70d0386abe02879ebaead2f9632dd2acb71000b4721fd8c1a2fb8c031a38d4d5"}, + {file = "orjson-3.6.7-cp39-none-win_amd64.whl", hash = "sha256:d9a3288861bfd26f3511fb4081561ca768674612bac59513cb9081bb61fcc87f"}, + {file = "orjson-3.6.7.tar.gz", hash = "sha256:a4bb62b11289b7620eead2f25695212e9ac77fcfba76f050fa8a540fb5c32401"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, @@ -1237,8 +1300,8 @@ pillow = [ {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] platformdirs = [ - {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, - {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -1435,9 +1498,13 @@ pymongo = [ {file = "pymongo-3.12.3-py2.7-macosx-10.14-intel.egg", hash = "sha256:d81299f63dc33cc172c26faf59cc54dd795fc6dd5821a7676cca112a5ee8bbd6"}, {file = "pymongo-3.12.3.tar.gz", hash = "sha256:0a89cadc0062a5e53664dde043f6c097172b8c1c5f0094490095282ff9995a5f"}, ] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] python-gitlab = [ - {file = "python-gitlab-3.1.1.tar.gz", hash = "sha256:cad1338c1ff1a791a7bae7995dc621e26c77dfbf225bade456acec7512782825"}, - {file = "python_gitlab-3.1.1-py3-none-any.whl", hash = "sha256:2a7de39c8976db6d0db20031e71b3e43d262e99e64b471ef09bf00482cd3d9fa"}, + {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, + {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, ] python-lsp-jsonrpc = [ {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"}, @@ -1495,8 +1562,8 @@ requests-toolbelt = [ {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] rope = [ - {file = "rope-0.22.0-py3-none-any.whl", hash = "sha256:2847220bf72ead09b5abe72b1edc9cacff90ab93663ece06913fc97324167870"}, - {file = "rope-0.22.0.tar.gz", hash = "sha256:b00fbc064a26fc62d7220578a27fd639b2fad57213663cc396c137e92d73f10f"}, + {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, + {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -1515,8 +1582,12 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tweepy = [ - {file = "tweepy-4.5.0-py2.py3-none-any.whl", hash = "sha256:1efe228d5994e0d996577bd052b73c59dada96ff8045e176bf46c175afe61859"}, - {file = "tweepy-4.5.0.tar.gz", hash = "sha256:12cc4b0a3d7b745928b08c3eb55a992236895e00028584d11fa41258f07df1b9"}, + {file = "tweepy-4.7.0-py2.py3-none-any.whl", hash = "sha256:d7e78c49232e849b660d82c7c2e504e8487d8014dcb73b39b490b61827965aba"}, + {file = "tweepy-4.7.0.tar.gz", hash = "sha256:82323505d549e3868e14a4570fc069ab3058ef95f9e578d1476d69bf2c831483"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] ujson = [ {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, From b6551c6c37aa1202158b00ecdc11954d1a4a1f42 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 19:43:15 -0600 Subject: [PATCH 105/365] Bump version --- jarvis/__init__.py | 9 ++++++--- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 38c27d8..5c7e26c 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,14 +1,19 @@ """Main J.A.R.V.I.S. package.""" import logging +from importlib.metadata import version as _v from dis_snek import Intents from jarvis_core.db import connect -# from jarvis import logo # noqa: F401 from jarvis import utils from jarvis.client import Jarvis from jarvis.config import get_config +try: + __version__ = _v("jarvis") +except Exception: + __version__ = "0.0.0" + jconfig = get_config() logger = logging.getLogger("discord") @@ -23,8 +28,6 @@ restart_ctx = None jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) -__version__ = "2.0.0a1" - async def run() -> None: """Run J.A.R.V.I.S.""" diff --git a/pyproject.toml b/pyproject.toml index 354687e..d63854d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jarvis" -version = "2.0.0a1" +version = "2.0.0b1" description = "J.A.R.V.I.S. admin bot" authors = ["Zevaryx "] From c00829f9492f3689db41f6a82797a1523b4165f8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 21:45:42 -0600 Subject: [PATCH 106/365] Fix GitLab key error on author name --- jarvis/cogs/gitlab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gitlab.py index f23062b..074a33f 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gitlab.py @@ -85,7 +85,7 @@ class GitlabCog(Scale): ) embed.set_author( name=issue.author["name"], - icon_url=issue.author["display_avatar"], + icon_url=issue.author["avatar_url"], url=issue.author["web_url"], ) embed.set_thumbnail( @@ -219,7 +219,7 @@ class GitlabCog(Scale): ) embed.set_author( name=mr.author["name"], - icon_url=mr.author["display_avatar"], + icon_url=mr.author["avatar_url"], url=mr.author["web_url"], ) embed.set_thumbnail( From 31df95809fa5fab6523ab21eadacc4cfc103188c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 18 Mar 2022 22:58:48 -0600 Subject: [PATCH 107/365] Allow users to open issues from Discord, closes #140 --- jarvis/cogs/{gitlab.py => gl.py} | 78 +++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 12 deletions(-) rename jarvis/cogs/{gitlab.py => gl.py} (83%) diff --git a/jarvis/cogs/gitlab.py b/jarvis/cogs/gl.py similarity index 83% rename from jarvis/cogs/gitlab.py rename to jarvis/cogs/gl.py index 074a33f..cdd08a4 100644 --- a/jarvis/cogs/gitlab.py +++ b/jarvis/cogs/gl.py @@ -1,16 +1,20 @@ """J.A.R.V.I.S. GitLab Cog.""" +import asyncio from datetime import datetime import gitlab from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import Embed, EmbedField +from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, slash_command, slash_option, ) +from dis_snek.models.snek.command import cooldown +from dis_snek.models.snek.cooldowns import Buckets from jarvis.config import get_config from jarvis.utils import build_embed @@ -29,7 +33,11 @@ class GitlabCog(Scale): self.project = self._gitlab.projects.get(29) @slash_command( - name="gl", sub_cmd_name="issue", description="Get an issue from GitLab", scopes=guild_ids + name="gl", + description="Get GitLab info", + sub_cmd_name="issue", + sub_cmd_description="Get an issue from GitLab", + scopes=guild_ids, ) @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) async def _issue(self, ctx: InteractionContext, id: int) -> None: @@ -96,7 +104,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="milestone", - description="Get a milestone from GitLab", + sub_cmd_description="Get a milestone from GitLab", scopes=guild_ids, ) @slash_option( @@ -152,7 +160,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="mr", - description="Get a merge request from GitLab", + sub_cmd_description="Get a merge request from GitLab", scopes=guild_ids, ) @slash_option( @@ -181,25 +189,26 @@ class GitlabCog(Scale): labels = "None" fields = [ - EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:]), - EmbedField(name="Assignee", value=assignee), - EmbedField(name="Labels", value=labels), + EmbedField(name="State", value=mr.state[0].upper() + mr.state[1:], inline=True), + EmbedField(name="Draft?", value=str(mr.draft), inline=True), + EmbedField(name="Assignee", value=assignee, inline=True), + EmbedField(name="Labels", value=labels, inline=True), ] if mr.labels: color = self.project.labels.get(mr.labels[0]).color else: color = "#00FFEE" - fields.append(EmbedField(name="Created At", value=created_at)) + fields.append(EmbedField(name="Created At", value=created_at, inline=True)) if mr.state == "merged": merged_at = datetime.strptime(mr.merged_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( "%Y-%m-%d %H:%M:%S UTC" ) - fields.append(EmbedField(name="Merged At", value=merged_at)) + fields.append(EmbedField(name="Merged At", value=merged_at, inline=True)) elif mr.state == "closed": closed_at = datetime.strptime(mr.closed_at, "%Y-%m-%dT%H:%M:%S.%fZ").strftime( "%Y-%m-%d %H:%M:%S UTC" ) - fields.append(EmbedField(name="Closed At", value=closed_at)) + fields.append(EmbedField(name="Closed At", value=closed_at, inline=True)) if mr.milestone: fields.append( EmbedField( @@ -261,7 +270,10 @@ class GitlabCog(Scale): return embed @slash_command( - name="gl", sub_cmd_name="issues", description="Get issues from GitLab", scopes=guild_ids + name="gl", + sub_cmd_name="issues", + sub_cmd_description="Get issues from GitLab", + scopes=guild_ids, ) @slash_option( name="state", @@ -316,7 +328,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="mrs", - description="Get merge requests from GitLab", + sub_cmd_description="Get merge requests from GitLab", scopes=guild_ids, ) @slash_option( @@ -374,7 +386,7 @@ class GitlabCog(Scale): @slash_command( name="gl", sub_cmd_name="milestones", - description="Get milestones from GitLab", + sub_cmd_description="Get milestones from GitLab", scopes=guild_ids, ) async def _milestones(self, ctx: InteractionContext) -> None: @@ -410,6 +422,48 @@ class GitlabCog(Scale): await paginator.send(ctx) + @slash_command( + name="gl", sub_cmd_name="report", sub_cmd_description="Report an issue", scopes=guild_ids + ) + @cooldown(bucket=Buckets.USER, rate=1, interval=600) + async def _open_issue(self, ctx: InteractionContext) -> None: + modal = Modal( + title="Open a new issue on GitLab", + components=[ + InputText( + label="Issue Title", + placeholder="Descriptive Title", + style=TextStyles.SHORT, + custom_id="title", + max_length=200, + ), + InputText( + label="Description (supports Markdown!)", + placeholder="Detailed Description", + style=TextStyles.PARAGRAPH, + custom_id="description", + ), + ], + ) + await ctx.send_modal(modal) + try: + resp = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + title = resp.responses.get("title") + desc = resp.responses.get("description") + except asyncio.TimeoutError: + return + if not title.startswith("[Discord]"): + title = "[Discord] " + title + desc = f"Opened by `@{ctx.author.username}` on Discord\n\n" + desc + issue = self.project.issues.create(data={"title": title, "description": desc}) + embed = build_embed( + title=f"Issue #{issue.id} Created", + description=("Thank you for opening an issue!\n\n[View it online]({issue['web_url']})"), + fields=[], + color="#00FFEE", + ) + await resp.send(embed=embed) + def setup(bot: Snake) -> None: """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" From 1fc0aec8f60baec38a47a839012f1f0d15f58d6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 20 Mar 2022 00:26:54 -0600 Subject: [PATCH 108/365] Add thread option to autoreact --- jarvis/client.py | 5 +++ jarvis/cogs/autoreact.py | 74 +++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 80c1c9d..c0ebc81 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -181,6 +181,11 @@ class Jarvis(Snake): if autoreact: for reaction in autoreact.reactions: await message.add_reaction(reaction) + if autoreact.thread: + name = message.content + if len(name) > 100: + name = name[:97] + "..." + await message.create_thread(name=message.content, reason="Autoreact") async def checks(self, message: Message) -> None: """Other message checks.""" diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index d5d127b..b5caa19 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -26,7 +26,7 @@ class AutoReactCog(Scale): self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") async def create_autoreact( - self, ctx: InteractionContext, channel: GuildText + self, ctx: InteractionContext, channel: GuildText, thread: bool ) -> Tuple[bool, Optional[str]]: """ Create an autoreact monitor on a channel. @@ -34,6 +34,7 @@ class AutoReactCog(Scale): Args: ctx: Interaction context of command channel: Channel to monitor + thread: Create a thread Returns: Tuple of success? and error message @@ -42,12 +43,13 @@ class AutoReactCog(Scale): if exists: return False, f"Autoreact already exists for {channel.mention}." - _ = Autoreact( + await Autoreact( guild=ctx.guild.id, channel=channel.id, reactions=[], + thread=thread, admin=ctx.author.id, - ).save() + ).commit() return True, None @@ -62,7 +64,11 @@ class AutoReactCog(Scale): Returns: Success? """ - return Autoreact.objects(guild=ctx.guild.id, channel=channel.id).delete() is not None + ar = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) + if ar: + await ar.delete() + return True + return False @slash_command( name="autoreact", @@ -76,43 +82,55 @@ class AutoReactCog(Scale): required=True, ) @slash_option( - name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=True + name="thread", description="Create a thread?", opt_type=OptionTypes.BOOLEAN, required=False + ) + @slash_option( + name="emote", description="Emote to add", opt_type=OptionTypes.STRING, required=False ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _autoreact_add(self, ctx: InteractionContext, channel: GuildText, emote: str) -> None: + async def _autoreact_add( + self, ctx: InteractionContext, channel: GuildText, thread: bool = True, emote: str = None + ) -> None: await ctx.defer() - custom_emoji = self.custom_emote.match(emote) - standard_emoji = emote in emoji_list - if not custom_emoji and not standard_emoji: - await ctx.send( - "Please use either an emote from this server or a unicode emoji.", - ephemeral=True, - ) - return - if custom_emoji: - emoji_id = int(custom_emoji.group(1)) - if not find(lambda x: x.id == emoji_id, ctx.guild.emojis): - await ctx.send("Please use a custom emote from this server.", ephemeral=True) + if emote: + custom_emoji = self.custom_emote.match(emote) + standard_emoji = emote in emoji_list + if not custom_emoji and not standard_emoji: + await ctx.send( + "Please use either an emote from this server or a unicode emoji.", + ephemeral=True, + ) return + if custom_emoji: + emoji_id = int(custom_emoji.group(1)) + if not find(lambda x: x.id == emoji_id, await ctx.guild.fetch_all_custom_emojis()): + await ctx.send("Please use a custom emote from this server.", ephemeral=True) + return autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) if not autoreact: - self.create_autoreact(ctx, channel) + await self.create_autoreact(ctx, channel, thread) autoreact = await Autoreact.find_one(q(guild=ctx.guild.id, channel=channel.id)) - if emote in autoreact.reactions: + if emote and emote in autoreact.reactions: await ctx.send( f"Emote already added to {channel.mention} autoreactions.", ephemeral=True, ) return - if len(autoreact.reactions) >= 5: + if emote and len(autoreact.reactions) >= 5: await ctx.send( "Max number of reactions hit. Remove a different one to add this one", ephemeral=True, ) return - autoreact.reactions.append(emote) - autoreact.save() - await ctx.send(f"Added {emote} to {channel.mention} autoreact.") + if emote: + autoreact.reactions.append(emote) + autoreact.thread = thread + await autoreact.commit() + message = "" + if emote: + message += f" Added {emote} to {channel.mention} autoreact." + message += f" Set autoreact thread creation to {thread} in {channel.mention}" + await ctx.send(message) @slash_command( name="autoreact", @@ -143,7 +161,7 @@ class AutoReactCog(Scale): ) return if emote.lower() == "all": - self.delete_autoreact(ctx, channel) + await self.delete_autoreact(ctx, channel) await ctx.send(f"Autoreact removed from {channel.mention}") elif emote not in autoreact.reactions: await ctx.send( @@ -153,9 +171,9 @@ class AutoReactCog(Scale): return else: autoreact.reactions.remove(emote) - autoreact.save() - if len(autoreact.reactions) == 0: - self.delete_autoreact(ctx, channel) + await autoreact.commit() + if len(autoreact.reactions) == 0 and not autoreact.thread: + await self.delete_autoreact(ctx, channel) await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") @slash_command( From 54dd6f40a88b24f21617c613bf2b5f6ab22b780b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 20 Mar 2022 02:00:29 -0600 Subject: [PATCH 109/365] Fix command descriptions --- jarvis/cogs/admin/roleping.py | 5 ++++- jarvis/cogs/rolegiver.py | 5 ++++- jarvis/cogs/starboard.py | 18 +++++++++++++++--- jarvis/cogs/twitter.py | 15 ++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index a9bf020..38a3c87 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -22,7 +22,10 @@ class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" @slash_command( - name="roleping", sub_cmd_name="add", sub_cmd_description="Add a role to roleping" + name="roleping", + description="Set up warnings for pinging specific roles", + sub_cmd_name="add", + sub_cmd_description="Add a role to roleping", ) @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 7a115b9..ea6b148 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -27,7 +27,10 @@ class RolegiverCog(Scale): self.bot = bot @slash_command( - name="rolegiver", sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver" + name="rolegiver", + description="Allow users to choose their own roles", + sub_cmd_name="add", + sub_cmd_description="Add a role to rolegiver", ) @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 8ae8da8..871e25c 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -33,7 +33,12 @@ class StarboardCog(Scale): def __init__(self, bot: Snake): self.bot = bot - @slash_command(name="starboard", sub_cmd_name="list", sub_cmd_description="List all starboards") + @slash_command( + name="starboard", + description="Extra pins! Manage starboards", + sub_cmd_name="list", + sub_cmd_description="List all starboards", + ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _list(self, ctx: InteractionContext) -> None: starboards = await Starboard.find(q(guild=ctx.guild.id)).to_list(None) @@ -221,7 +226,12 @@ class StarboardCog(Scale): async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add(ctx, message=str(ctx.target_id)) - @slash_command(name="star", sub_cmd_name="add", description="Star a message") + @slash_command( + name="star", + description="Manage stars", + sub_cmd_name="add", + sub_cmd_description="Star a message", + ) @slash_option( name="message", description="Message to star", opt_type=OptionTypes.STRING, required=True ) @@ -237,7 +247,9 @@ class StarboardCog(Scale): ) -> None: await self._star_add(ctx, message, channel) - @slash_command(name="star", sub_cmd_name="delete", description="Delete a starred message") + @slash_command( + name="star", sub_cmd_name="delete", sub_cmd_description="Delete a starred message" + ) @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True ) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index dc7d875..836b57b 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -33,7 +33,12 @@ class TwitterCog(Scale): self._guild_cache = {} self._channel_cache = {} - @slash_command(name="twitter", sub_cmd_name="follow", description="Follow a Twitter acount") + @slash_command( + name="twitter", + description="Manage Twitter follows", + sub_cmd_name="follow", + sub_cmd_description="Follow a Twitter acount", + ) @slash_option( name="handle", description="Twitter account", opt_type=OptionTypes.STRING, required=True ) @@ -107,7 +112,9 @@ class TwitterCog(Scale): await ctx.send(f"Now following `@{handle}` in {channel.mention}") - @slash_command(name="twitter", sub_cmd_name="unfollow", description="Unfollow Twitter accounts") + @slash_command( + name="twitter", sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts" + ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) @@ -162,7 +169,9 @@ class TwitterCog(Scale): await message.edit(components=components) @slash_command( - name="twitter", sub_cmd_name="retweets", description="Modify followed Twitter accounts" + name="twitter", + sub_cmd_name="retweets", + sub_cmd_description="Modify followed Twitter accounts", ) @slash_option( name="retweets", From e80afc97ad0d2001a5ab8cf0463cdaf8408fd789 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:23:47 -0600 Subject: [PATCH 110/365] fix lock and lockdown cogs --- jarvis/cogs/admin/lock.py | 228 +++++++++++++++++----------------- jarvis/cogs/admin/lockdown.py | 214 +++++++++++++++++++------------ 2 files changed, 252 insertions(+), 190 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index e17cf58..eefdada 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,113 +1,117 @@ """J.A.R.V.I.S. LockCog.""" -# from dis_snek import Scale -# -# # TODO: Uncomment 99% of code once implementation is figured out -# from contextlib import suppress -# from typing import Union -# -# from dis_snek import InteractionContext, Scale, Snake -# from dis_snek.models.discord.enums import Permissions -# from dis_snek.models.discord.role import Role -# from dis_snek.models.discord.user import User -# from dis_snek.models.discord.channel import GuildText, GuildVoice, PermissionOverwrite -# from dis_snek.models.snek.application_commands import ( -# OptionTypes, -# PermissionTypes, -# slash_command, -# slash_option, -# ) -# from dis_snek.models.snek.command import check -# -# from jarvis.db.models import Lock -# from jarvis.utils.permissions import admin_or_permissions -# -# -# class LockCog(Scale): -# """J.A.R.V.I.S. LockCog.""" -# -# @slash_command(name="lock", description="Lock a channel") -# @slash_option(name="reason", -# description="Lock Reason", -# opt_type=3, -# required=True,) -# @slash_option(name="duration", -# description="Lock duration in minutes (default 10)", -# opt_type=4, -# required=False,) -# @slash_option(name="channel", -# description="Channel to lock", -# opt_type=7, -# required=False,) -# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) -# async def _lock( -# self, -# ctx: InteractionContext, -# reason: str, -# duration: int = 10, -# channel: Union[GuildText, GuildVoice] = None, -# ) -> None: -# await ctx.defer(ephemeral=True) -# if duration <= 0: -# await ctx.send("Duration must be > 0", ephemeral=True) -# return -# -# elif duration > 60 * 12: -# await ctx.send("Duration must be <= 12 hours", ephemeral=True) -# return -# -# if len(reason) > 100: -# await ctx.send("Reason must be <= 100 characters", ephemeral=True) -# return -# if not channel: -# channel = ctx.channel -# -# # role = ctx.guild.default_role # Uncomment once implemented -# if isinstance(channel, GuildText): -# to_deny = Permissions.SEND_MESSAGES -# elif isinstance(channel, GuildVoice): -# to_deny = Permissions.CONNECT | Permissions.SPEAK -# -# overwrite = PermissionOverwrite(type=PermissionTypes.ROLE, deny=to_deny) -# # TODO: Get original permissions -# # TODO: Apply overwrite -# overwrite = overwrite -# _ = Lock( -# channel=channel.id, -# guild=ctx.guild.id, -# admin=ctx.author.id, -# reason=reason, -# duration=duration, -# ) # .save() # Uncomment once implemented -# # await ctx.send(f"{channel.mention} locked for {duration} minute(s)") -# await ctx.send("Unfortunately, this is not yet implemented", hidden=True) -# -# @cog_ext.cog_slash( -# name="unlock", -# description="Unlocks a channel", -# choices=[ -# create_option( -# name="channel", -# description="Channel to lock", -# opt_type=7, -# required=False, -# ), -# ], -# ) -# @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) -# async def _unlock( -# self, -# ctx: InteractionContext, -# channel: Union[GuildText, GuildVoice] = None, -# ) -> None: -# if not channel: -# channel = ctx.channel -# lock = Lock.objects(guild=ctx.guild.id, channel=channel.id, active=True).first() -# if not lock: -# await ctx.send(f"{channel.mention} not locked.", ephemeral=True) -# return -# for role in ctx.guild.roles: -# with suppress(Exception): -# await self._unlock_channel(channel, role, ctx.author) -# lock.active = False -# lock.save() -# await ctx.send(f"{channel.mention} unlocked") +from typing import Union + +from dis_snek import InteractionContext, Scale +from dis_snek.client.utils.misc_utils import get +from dis_snek.models.discord.channel import GuildText, GuildVoice +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Permission + +from jarvis.utils.permissions import admin_or_permissions + + +class LockCog(Scale): + """J.A.R.V.I.S. LockCog.""" + + @slash_command(name="lock", description="Lock a channel") + @slash_option( + name="reason", + description="Lock Reason", + opt_type=3, + required=True, + ) + @slash_option( + name="duration", + description="Lock duration in minutes (default 10)", + opt_type=4, + required=False, + ) + @slash_option( + name="channel", + description="Channel to lock", + opt_type=7, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) + async def _lock( + self, + ctx: InteractionContext, + reason: str, + duration: int = 10, + channel: Union[GuildText, GuildVoice] = None, + ) -> None: + await ctx.defer(ephemeral=True) + if duration <= 0: + await ctx.send("Duration must be > 0", ephemeral=True) + return + + elif duration > 60 * 12: + await ctx.send("Duration must be <= 12 hours", ephemeral=True) + return + + if len(reason) > 100: + await ctx.send("Reason must be <= 100 characters", ephemeral=True) + return + if not channel: + channel = ctx.channel + + # role = ctx.guild.default_role # Uncomment once implemented + if isinstance(channel, GuildText): + to_deny = Permissions.SEND_MESSAGES + elif isinstance(channel, GuildVoice): + to_deny = Permissions.CONNECT | Permissions.SPEAK + + current = get(channel.permission_overwrites, id=ctx.guild.id) + if current: + current = Permission(id=ctx.guild.id, allow=int(current.allow), deny=int(current.deny)) + role = await ctx.guild.fetch_role(ctx.guild.id) + + await channel.add_permission(target=role, deny=to_deny, reason="Locked") + await Lock( + channel=channel.id, + guild=ctx.guild.id, + admin=ctx.author.id, + reason=reason, + duration=duration, + original_perms=current, + ).commit() + await ctx.send(f"{channel.mention} locked for {duration} minute(s)") + + @slash_command(name="unlock", description="Unlock a channel") + @slash_option( + name="channel", + description="Channel to unlock", + opt_type=OptionTypes.CHANNEL, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) + async def _unlock( + self, + ctx: InteractionContext, + channel: Union[GuildText, GuildVoice] = None, + ) -> None: + if not channel: + channel = ctx.channel + lock = await Lock.find_one(q(guild=ctx.guild.id, channel=channel.id, active=True)) + if not lock: + await ctx.send(f"{channel.mention} not locked.", ephemeral=True) + return + + overwrite = get(channel.permission_overwrites, id=ctx.guild.id) + if overwrite and lock.original_perms: + overwrite.allow = lock.original_perms.allow + overwrite.deny = lock.original_perms.deny + await channel.edit_permission(overwrite, reason="Unlock") + elif overwrite and not lock.original_perms: + await channel.delete_permission(target=overwrite, reason="Unlock") + + lock.active = False + await lock.commit() + await ctx.send(f"{channel.mention} unlocked") diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index cd711fb..14407d8 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,101 +1,159 @@ """J.A.R.V.I.S. LockdownCog.""" -from contextlib import suppress -from datetime import datetime +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.client.utils.misc_utils import find_all, get +from dis_snek.models.discord.channel import GuildCategory, GuildChannel +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.discord.guild import Guild +from dis_snek.models.discord.user import Member +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Lockdown, Permission -from discord.ext import commands -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option - -from jarvis.db.models import Lock -from jarvis.utils.cachecog import CacheCog - -# from jarvis.utils.permissions import admin_or_permissions +from jarvis.utils.permissions import admin_or_permissions -class LockdownCog(CacheCog): +async def lock(bot: Snake, target: GuildChannel, admin: Member, reason: str, duration: int) -> None: + """ + Lock an existing channel + + Args: + bot: Bot instance + target: Target channel + admin: Admin who initiated lockdown + """ + to_deny = Permissions.SEND_MESSAGES | Permissions.CONNECT | Permissions.SPEAK + current = get(target.permission_overwrites, id=target.guild.id) + if current: + current = Permission(id=target.guild.id, allow=int(current.allow), deny=int(current.deny)) + role = await target.guild.fetch_role(target.guild.id) + await target.add_permission(target=role, deny=to_deny, reason="Lockdown") + await Lock( + channel=target.id, + guild=target.guild.id, + admin=admin.id, + reason=reason, + duration=duration, + original_perms=current, + ).commit() + + +async def lock_all(bot: Snake, guild: Guild, admin: Member, reason: str, duration: int) -> None: + """ + Lock all channels + + Args: + bot: Bot instance + guild: Target guild + admin: Admin who initiated lockdown + """ + role = await guild.fetch_role(guild.id) + categories = find_all(lambda x: isinstance(x, GuildCategory), guild.channels) + for category in categories: + await lock(bot, category, admin, reason, duration) + perms = category.permissions_for(role) + + for channel in category.channels: + if perms != channel.permissions_for(role): + await lock(bot, channel, admin, reason, duration) + + +async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: + """ + Unlock all locked channels + + Args: + bot: Bot instance + target: Target channel + admin: Admin who ended lockdown + """ + locks = Lock.find(q(guild=guild.id, active=True)) + async for lock in locks: + target = await guild.fetch_channel(lock.channel) + if target: + overwrite = get(target.permission_overwrites, id=guild.id) + if overwrite and lock.original_perms: + overwrite.allow = lock.original_perms.allow + overwrite.deny = lock.original_perms.deny + await target.edit_permission(overwrite, reason="Lockdown end") + elif overwrite and not lock.original_perms: + await target.delete_permission(target=overwrite, reason="Lockdown end") + lock.active = False + await lock.commit() + lockdown = await Lockdown.find_one(q(guild=guild.id, active=True)) + if lockdown: + lockdown.active = False + await lockdown.commit() + + +class LockdownCog(Scale): """J.A.R.V.I.S. LockdownCog.""" - def __init__(self, bot: commands.Bot): - super().__init__(bot) - - @cog_ext.cog_subcommand( - base="lockdown", - name="start", - description="Locks a server", - choices=[ - create_option( - name="reason", - description="Lockdown Reason", - opt_type=3, - required=True, - ), - create_option( - name="duration", - description="Lockdown duration in minutes (default 10)", - opt_type=4, - required=False, - ), - ], + @slash_command( + name="lockdown", + description="Manage server-wide lockdown", + sub_cmd_name="start", + sub_cmd_description="Lockdown the server", ) - # @check(admin_or_permissions(manage_channels=True)) + @slash_option( + name="reason", description="Lockdown reason", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="duration", + description="Duration in minutes", + opt_type=OptionTypes.INTEGER, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_start( self, - ctx: SlashContext, + ctx: InteractionContext, reason: str, duration: int = 10, ) -> None: - await ctx.defer(ephemeral=True) + await ctx.defer() if duration <= 0: await ctx.send("Duration must be > 0", ephemeral=True) return elif duration >= 300: await ctx.send("Duration must be < 5 hours", ephemeral=True) return - channels = ctx.guild.channels - roles = ctx.guild.roles - updates = [] - for channel in channels: - for role in roles: - with suppress(Exception): - await self._lock_channel(channel, role, ctx.author, reason) - updates.append( - Lock( - channel=channel.id, - guild=ctx.guild.id, - admin=ctx.author.id, - reason=reason, - duration=duration, - active=True, - created_at=datetime.utcnow(), - ) - ) - if updates: - Lock.objects().insert(updates) - await ctx.send(f"Server locked for {duration} minute(s)") - @cog_ext.cog_subcommand( - base="lockdown", - name="end", - description="Unlocks a server", - ) - @commands.has_permissions(administrator=True) + exists = await Lockdown.find_one(q(guild=ctx.guild.id, active=True)) + if exists: + await ctx.send("Server already in lockdown", ephemeral=True) + return + + await lock_all(self.bot, ctx.guild, ctx.author, reason, duration) + role = await ctx.guild.fetch_role(ctx.guild.id) + original_perms = role.permissions + new_perms = role.permissions & ~Permissions.SEND_MESSAGES + await role.edit(permissions=new_perms) + await Lockdown( + admin=ctx.author.id, + duration=duration, + guild=ctx.guild.id, + reason=reason, + original_perms=int(original_perms), + ).commit() + await ctx.send("Server now in lockdown.") + + @slash_command(name="lockdown", sub_cmd_name="end", sub_cmd_description="End a lockdown") + @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_end( self, - ctx: SlashContext, + ctx: InteractionContext, ) -> None: - channels = ctx.guild.channels - roles = ctx.guild.roles - update = False - locks = Lock.objects(guild=ctx.guild.id, active=True) - if not locks: - await ctx.send("No lockdown detected.", ephemeral=True) - return await ctx.defer() - for channel in channels: - for role in roles: - with suppress(Exception): - await self._unlock_channel(channel, role, ctx.author) - update = True - if update: - Lock.objects(guild=ctx.guild.id, active=True).update(set__active=False) - await ctx.send("Server unlocked") + + lockdown = await Lockdown.find_one(q(guild=ctx.guild.id, active=True)) + if not lockdown: + await ctx.send("Server not in lockdown", ephemeral=True) + return + + await unlock_all(self.bot, ctx.guild, ctx.author) + await ctx.send("Server no longer in lockdown.") From 2308342de767dc98c552879f77287b24e58b6a91 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:24:53 -0600 Subject: [PATCH 111/365] Create ModlogCase scale wrapper --- jarvis/cogs/admin/__init__.py | 6 +-- jarvis/cogs/admin/ban.py | 11 ++++- jarvis/cogs/admin/kick.py | 5 ++- jarvis/cogs/admin/mute.py | 8 ++-- jarvis/cogs/admin/warning.py | 5 ++- jarvis/utils/cachecog.py | 35 ---------------- jarvis/utils/cogs.py | 75 +++++++++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 49 deletions(-) delete mode 100644 jarvis/utils/cachecog.py create mode 100644 jarvis/utils/cogs.py diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index b11ccd4..daaca31 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,15 +1,15 @@ """J.A.R.V.I.S. Admin Cogs.""" from dis_snek import Snake -from jarvis.cogs.admin import ban, kick, mute, purge, roleping, warning +from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning def setup(bot: Snake) -> None: """Add admin cogs to J.A.R.V.I.S.""" ban.BanCog(bot) kick.KickCog(bot) - # lock.LockCog(bot) - # lockdown.LockdownCog(bot) + lock.LockCog(bot) + lockdown.LockdownCog(bot) mute.MuteCog(bot) purge.PurgeCog(bot) roleping.RolepingCog(bot) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index f4742cb..9b98ec9 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. BanCog.""" import re -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -17,10 +17,11 @@ from jarvis_core.db import q from jarvis_core.db.models import Ban, Unban from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class BanCog(Scale): +class BanCog(ModcaseCog): """J.A.R.V.I.S. BanCog.""" async def discord_apply_ban( @@ -105,6 +106,12 @@ class BanCog(Scale): SlashCommandChoice(name="Soft", value="soft"), ], ) + @slash_option( + name="duration", + description="Temp ban duration in hours", + opt_type=OptionTypes.INTEGER, + required=False, + ) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _ban( self, diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 3bb7cfb..8a3a6b1 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. KickCog.""" -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( @@ -11,10 +11,11 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Kick from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class KickCog(Scale): +class KickCog(ModcaseCog): """J.A.R.V.I.S. KickCog.""" @slash_command(name="kick", description="Kick a user") diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 5ac5ec5..ffb634f 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,7 +1,7 @@ """J.A.R.V.I.S. MuteCog.""" from datetime import datetime -from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( @@ -14,15 +14,13 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Mute from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions -class MuteCog(Scale): +class MuteCog(ModcaseCog): """J.A.R.V.I.S. MuteCog.""" - def __init__(self, bot: Snake): - self.bot = bot - @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 7e60987..0ef4d62 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions, Scale +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -14,11 +14,12 @@ from dis_snek.models.snek.command import check from jarvis_core.db.models import Warning from jarvis.utils import build_embed +from jarvis.utils.cogs import ModcaseCog from jarvis.utils.embeds import warning_embed from jarvis.utils.permissions import admin_or_permissions -class WarningCog(Scale): +class WarningCog(ModcaseCog): """J.A.R.V.I.S. WarningCog.""" @slash_command(name="warn", description="Warn a user") diff --git a/jarvis/utils/cachecog.py b/jarvis/utils/cachecog.py deleted file mode 100644 index d7be74b..0000000 --- a/jarvis/utils/cachecog.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Cog wrapper for command caching.""" -from datetime import datetime, timedelta - -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import find -from dis_snek.ext.tasks.task import Task -from dis_snek.ext.tasks.triggers import IntervalTrigger - - -class CacheCog(Scale): - """Cog wrapper for command caching.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.cache = {} - self._expire_interaction.start() - - def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: - """Check the cache.""" - if not kwargs: - kwargs = {} - return find( - lambda x: x["command"] == ctx.subcommand_name # noqa: W503 - and x["user"] == ctx.author.id # noqa: W503 - and x["guild"] == ctx.guild.id # noqa: W503 - and all(x[k] == v for k, v in kwargs.items()), # noqa: W503 - self.cache.values(), - ) - - @Task.create(IntervalTrigger(minutes=1)) - async def _expire_interaction(self) -> None: - keys = list(self.cache.keys()) - for key in keys: - if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): - del self.cache[key] diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py new file mode 100644 index 0000000..f9361c3 --- /dev/null +++ b/jarvis/utils/cogs.py @@ -0,0 +1,75 @@ +"""Cog wrapper for command caching.""" +from datetime import datetime, timedelta + +from dis_snek import Context, Scale, Snake +from dis_snek.client.utils.misc_utils import find +from dis_snek.models.snek.tasks.task import Task +from dis_snek.models.snek.tasks.triggers import IntervalTrigger +from jarvis_core.db import q +from jarvis_core.db.models import Action, Ban, Kick, Modlog, Mute, Note, Warning + +MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} + + +class CacheCog(Scale): + """Cog wrapper for command caching.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.cache = {} + self._expire_interaction.start() + + def check_cache(self, ctx: Context, **kwargs: dict) -> dict: + """Check the cache.""" + if not kwargs: + kwargs = {} + return find( + lambda x: x["command"] == ctx.subcommand_name # noqa: W503 + and x["user"] == ctx.author.id # noqa: W503 + and x["guild"] == ctx.guild.id # noqa: W503 + and all(x[k] == v for k, v in kwargs.items()), # noqa: W503 + self.cache.values(), + ) + + @Task.create(IntervalTrigger(minutes=1)) + async def _expire_interaction(self) -> None: + keys = list(self.cache.keys()) + for key in keys: + if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): + del self.cache[key] + + +class ModcaseCog(Scale): + """Cog wrapper for moderation case logging.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.add_scale_postrun(self.log) + + async def log(self, ctx: Context, *args: list, **kwargs: dict) -> None: + """ + Log a moderation activity in a moderation case. + + Args: + ctx: Command context + """ + name = self.__name__.replace("Cog", "") + + if name not in ["Lock", "Lockdown", "Purge", "Roleping"]: + user = kwargs.pop("user", None) + if not user: + # Log warning about missing user + return + coll = MODLOG_LOOKUP.get(name, None) + if not coll: + # Log warning about unsupported action + return + + action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) + if not action: + # Log warning about missing action + return + + action = Action(action_type=name.lower(), parent=action.id) + note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") + await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() From d3449fc5ed5cd84a33b80760a18bb048c63065af Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:25:30 -0600 Subject: [PATCH 112/365] Allow giving credit to other users for GitLab issues --- jarvis/cogs/gl.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index cdd08a4..f8dbdfd 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -7,6 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles +from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -422,11 +423,16 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", sub_cmd_name="report", sub_cmd_description="Report an issue", scopes=guild_ids + @slash_command(name="issue", description="Report an issue on GitLab", scopes=guild_ids) + @slash_option( + name="user", + description="Credit someone else for this issue", + opt_type=OptionTypes.USER, + required=False, ) @cooldown(bucket=Buckets.USER, rate=1, interval=600) - async def _open_issue(self, ctx: InteractionContext) -> None: + async def _open_issue(self, ctx: InteractionContext, user: Member = None) -> None: + user = user or ctx.author modal = Modal( title="Open a new issue on GitLab", components=[ @@ -454,7 +460,7 @@ class GitlabCog(Scale): return if not title.startswith("[Discord]"): title = "[Discord] " + title - desc = f"Opened by `@{ctx.author.username}` on Discord\n\n" + desc + desc = f"Opened by `@{user.username}` on Discord\n\n" + desc issue = self.project.issues.create(data={"title": title, "description": desc}) embed = build_embed( title=f"Issue #{issue.id} Created", From e59e566f30a73749a8a3554b8808387bba29a04d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:25:47 -0600 Subject: [PATCH 113/365] Fix reminders, use timestamps --- jarvis/cogs/remindme.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index e53a532..002964e 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -113,7 +113,7 @@ class RemindmeCog(Scale): ) return - remind_at = datetime.now() + timedelta(**delta) + remind_at = datetime.utcnow() + timedelta(**delta) r = Reminder( user=ctx.author.id, @@ -134,7 +134,7 @@ class RemindmeCog(Scale): EmbedField(name="Message", value=message), EmbedField( name="When", - value=remind_at.strftime("%Y-%m-%d %H:%M UTC"), + value=f"", inline=False, ), ], @@ -157,7 +157,7 @@ class RemindmeCog(Scale): if reminder.private and isinstance(ctx.channel, GuildChannel): fields.embed( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value="Please DM me this command to view the content of this reminder", inline=False, ) @@ -165,7 +165,7 @@ class RemindmeCog(Scale): else: fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value=f"{reminder.message}\n\u200b", inline=False, ) @@ -187,15 +187,7 @@ class RemindmeCog(Scale): @slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders") async def _list(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return - reminders = await Reminder.find(q(user=ctx.author.id, active=True)) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: await ctx.send("You have no reminders set.", ephemeral=True) return @@ -206,7 +198,7 @@ class RemindmeCog(Scale): @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: - reminders = await Reminder.find(q(user=ctx.author.id, active=True)) + reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: await ctx.send("You have no reminders set", ephemeral=True) return @@ -214,7 +206,7 @@ class RemindmeCog(Scale): options = [] for reminder in reminders: option = SelectOption( - label=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + label=f"", value=str(reminder.id), emoji="⏰", ) @@ -249,7 +241,7 @@ class RemindmeCog(Scale): if reminder.private and isinstance(ctx.channel, GuildChannel): fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value="Private reminder", inline=False, ) @@ -257,7 +249,7 @@ class RemindmeCog(Scale): else: fields.append( EmbedField( - name=reminder.remind_at.strftime("%Y-%m-%d %H:%M UTC"), + name=f"", value=reminder.message, inline=False, ) From 32841b230aa6258fd6c0462988938485239c9842 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 01:26:24 -0600 Subject: [PATCH 114/365] More work on settings, WIP --- jarvis/cogs/settings.py | 47 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index c197573..de39409 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,9 +1,16 @@ """J.A.R.V.I.S. Settings Management Cog.""" from typing import Any +from dis_snek import InteractionContext, Scale, Snake +from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.enums import Permissions +from dis_snek.models.snek.application_commands import ( + OptionTypes, + slash_command, + slash_option, +) from dis_snek.models.snek.command import check from discord import Role, TextChannel -from discord.ext import commands from discord.utils import find from discord_slash import SlashContext, cog_ext from discord_slash.utils.manage_commands import create_option @@ -15,12 +22,9 @@ from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions -class SettingsCog(commands.Cog): +class SettingsCog(Scale): """J.A.R.V.I.S. Settings Management Cog.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" existing = await Setting.find_one(q(setting=setting, guild=guild)) @@ -38,24 +42,19 @@ class SettingsCog(commands.Cog): return await existing.delete() return False - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="modlog", - description="Set modlog channel", - choices=[ - create_option( - name="channel", - description="Modlog channel", - opt_type=7, - required=True, - ) - ], + @slash_command( + name="settings", + description="Control bot settings", + sub_cmd_name="modlog", + sub_cmd_description="Set ActivityLog channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_modlog(self, ctx: SlashContext, channel: TextChannel) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", ephemeral=True) + @slash_option( + name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_modlog(self, ctx: InteractionContext(), channel: GuildText) -> None: + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText", ephemeral=True) return self.update_settings("modlog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New modlog channel is {channel.mention}") @@ -74,7 +73,7 @@ class SettingsCog(commands.Cog): ) ], ) - @check(admin_or_permissions(manage_guild=True)) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None: if not isinstance(channel, TextChannel): await ctx.send("Channel must be a TextChannel", ephemeral=True) @@ -261,6 +260,6 @@ class SettingsCog(commands.Cog): await ctx.send(f"Guild settings cleared: `{deleted is not None}`") -def setup(bot: commands.Bot) -> None: +def setup(bot: Snake) -> None: """Add SettingsCog to J.A.R.V.I.S.""" SettingsCog(bot) From dc5919ff869009c975eca3390a129710e704562c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:30:14 -0600 Subject: [PATCH 115/365] Update config --- config.example.yaml | 44 +++++++++++++++++++++++++++----------------- jarvis/config.py | 6 +----- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index cfa17f6..6aa129a 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,24 +1,34 @@ --- - token: api key here - client_id: 123456789012345678 - logo: alligator2 + token: bot token + twitter: + consumer_key: key + consumer_secret: secret + access_token: access token + access_secret: access secret mongo: connect: - username: user - password: pass - host: localhost + username: username + password: password + host: hostname port: 27017 database: database urls: - url_name: url - url_name2: url2 - max_messages: 1000 - gitlab_token: null + extra: urls + max_messages: 10000 + gitlab_token: token cogs: - - list - - of - - enabled - - cogs - - all - - if - - empty + - admin + - autoreact + - dev + - image + - gl + - remindme + - rolegiver + # - settings + - starboard + - twitter + - util + - verify + log_level: INFO + sync: false + #sync_commands: True diff --git a/jarvis/config.py b/jarvis/config.py index e8d65bd..3ab039e 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -12,7 +12,7 @@ except ImportError: class JarvisConfig(CConfig): - REQUIRED = ["token", "client_id", "mongo", "urls"] + REQUIRED = ["token", "mongo", "urls"] OPTIONAL = { "sync": False, "log_level": "WARNING", @@ -39,8 +39,6 @@ class Config(object): def init( self, token: str, - client_id: str, - logo: str, mongo: dict, urls: dict, sync: bool = False, @@ -53,8 +51,6 @@ class Config(object): ) -> None: """Initialize the config object.""" self.token = token - self.client_id = client_id - self.logo = logo self.mongo = mongo self.urls = urls self.log_level = log_level From be6d88449ee867405a25ae768b8d24ba3738628a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:30:45 -0600 Subject: [PATCH 116/365] Fix verification --- jarvis/cogs/verify.py | 66 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 2255623..a4b0647 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -3,8 +3,8 @@ import asyncio from random import randint from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.application_commands import slash_command from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows +from dis_snek.models.snek.application_commands import slash_command from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -41,10 +41,10 @@ class VerifyCog(Scale): await ctx.defer() role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) if not role: - await ctx.send("This guild has not enabled verification", delete_after=5) + message = await ctx.send("This guild has not enabled verification", ephemeral=True) return - if await ctx.guild.get_role(role.value) in ctx.author.roles: - await ctx.send("You are already verified.", delete_after=5) + if await ctx.guild.fetch_role(role.value) in ctx.author.roles: + await ctx.send("You are already verified.", ephemeral=True) return components = create_layout() message = await ctx.send( @@ -53,35 +53,39 @@ class VerifyCog(Scale): ) try: - context = await self.bot.wait_for_component( - messages=message, check=lambda x: ctx.author.id == x.author.id, timeout=30 - ) - - correct = context.context.custom_id.split("||")[-1] == "yes" - if correct: - for row in components: - for component in row.components: - component.disabled = True - setting = await Setting.find_one(guild=ctx.guild.id, setting="verified") - role = await ctx.guild.get_role(setting.value) - await ctx.author.add_roles(role, reason="Verification passed") - setting = await Setting.find_one(guild=ctx.guild.id, setting="unverified") - if setting: - role = await ctx.guild.get_role(setting.value) - await ctx.author.remove_roles(role, reason="Verification passed") - - await context.context.edit_origin( - content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", - components=components, + verified = False + while not verified: + response = await self.bot.wait_for_component( + messages=message, + check=lambda x: ctx.author.id == x.context.author.id, + timeout=30, ) - await context.context.message.delete(delay=5) - else: - await context.context.edit_origin( - content=( - f"{ctx.author.mention}, incorrect. " - "Please press the button that says `YES`" + + correct = response.context.custom_id.split("||")[-1] == "yes" + if correct: + for row in components: + for component in row.components: + component.disabled = True + setting = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.add_role(role, reason="Verification passed") + setting = await Setting.find_one(q(guild=ctx.guild.id, setting="unverified")) + if setting: + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.remove_role(role, reason="Verification passed") + + await response.context.edit_origin( + content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", + components=components, + ) + await response.context.message.delete(delay=5) + else: + await response.context.edit_origin( + content=( + f"{ctx.author.mention}, incorrect. " + "Please press the button that says `YES`" + ) ) - ) except asyncio.TimeoutError: await message.delete(delay=30) From 75b850e73465c295654009710553b055831e8a8a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 09:55:51 -0600 Subject: [PATCH 117/365] Replace all custom booleans with standard booleans --- jarvis/cogs/admin/ban.py | 8 ++++---- jarvis/cogs/admin/warning.py | 11 ++--------- jarvis/cogs/remindme.py | 10 ++-------- jarvis/cogs/settings.py | 8 ++++---- jarvis/cogs/twitter.py | 19 ++++--------------- 5 files changed, 16 insertions(+), 40 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 9b98ec9..c0c8050 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -302,13 +302,13 @@ class BanCog(ModcaseCog): @slash_option( name="active", description="Active bans", - opt_type=OptionTypes.INTEGER, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[SlashCommandChoice(name="Yes", value=1), SlashCommandChoice(name="No", value=0)], ) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) - async def _bans_list(self, ctx: InteractionContext, btype: int = 0, active: int = 1) -> None: - active = bool(active) + async def _bans_list( + self, ctx: InteractionContext, btype: int = 0, active: bool = True + ) -> None: types = [0, "perm", "temp", "soft"] search = {"guild": ctx.guild.id} if active: diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 0ef4d62..86b08aa 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -6,7 +6,6 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -66,17 +65,11 @@ class WarningCog(ModcaseCog): @slash_option( name="active", description="View active only", - opt_type=OptionTypes.INTEGER, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value=1), - SlashCommandChoice(name="No", value=0), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _warnings(self, ctx: InteractionContext, user: User, active: bool = 1) -> None: - active = bool(active) - + async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: warnings = ( await Warning.find( user=user.id, diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 002964e..1b36042 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -13,7 +13,6 @@ from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -37,19 +36,14 @@ class RemindmeCog(Scale): @slash_option( name="private", description="Send as DM?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="y"), - SlashCommandChoice(name="No", value="n"), - ], ) async def _remindme( self, ctx: InteractionContext, - private: str = "n", + private: bool = False, ) -> None: - private = private == "y" modal = Modal( title="Set your reminder!", components=[ diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index de39409..fec3ee6 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -150,16 +150,16 @@ class SettingsCog(Scale): create_option( name="active", description="Active?", - opt_type=4, + opt_type=OptionTypes.BOOLEAN, required=True, ) ], ) @check(admin_or_permissions(manage_guild=True)) - async def _set_invitedel(self, ctx: SlashContext, active: int) -> None: + async def _set_invitedel(self, ctx: SlashContext, active: bool) -> None: await ctx.defer() - self.update_settings("noinvite", bool(active), ctx.guild.id) - await ctx.send(f"Settings applied. Automatic invite active: {bool(active)}") + self.update_settings("noinvite", active, ctx.guild.id) + await ctx.send(f"Settings applied. Automatic invite active: {active}") @cog_ext.cog_subcommand( base="settings", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 836b57b..79521a0 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -8,7 +8,6 @@ from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( OptionTypes, - SlashCommandChoice, slash_command, slash_option, ) @@ -51,19 +50,14 @@ class TwitterCog(Scale): @slash_option( name="retweets", description="Mirror re-tweets?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="Yes"), - SlashCommandChoice(name="No", value="No"), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_follow( - self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: str = "Yes" + self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: bool = True ) -> None: handle = handle.lower() - retweets = retweets == "Yes" if len(handle) > 15: await ctx.send("Invalid Twitter handle", ephemeral=True) return @@ -176,16 +170,11 @@ class TwitterCog(Scale): @slash_option( name="retweets", description="Mirror re-tweets?", - opt_type=OptionTypes.STRING, + opt_type=OptionTypes.BOOLEAN, required=False, - choices=[ - SlashCommandChoice(name="Yes", value="Yes"), - SlashCommandChoice(name="No", value="No"), - ], ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _twitter_modify(self, ctx: InteractionContext, retweets: str) -> None: - retweets = retweets == "Yes" + async def _twitter_modify(self, ctx: InteractionContext, retweets: bool = True) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) twitters = [] async for twitter in t: From b0c461ab7d25393840c23a832b2373c4be4193fa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 18:27:23 -0600 Subject: [PATCH 118/365] Finally add logging, use dateparser in RemindMe --- .flake8 | 1 + jarvis/__init__.py | 21 +++- jarvis/client.py | 19 +++- jarvis/cogs/admin/ban.py | 7 +- jarvis/cogs/admin/kick.py | 8 +- jarvis/cogs/admin/lock.py | 7 +- jarvis/cogs/admin/lockdown.py | 6 + jarvis/cogs/admin/mute.py | 7 +- jarvis/cogs/admin/purge.py | 3 + jarvis/cogs/admin/roleping.py | 8 +- jarvis/cogs/admin/warning.py | 19 +++- jarvis/cogs/autoreact.py | 2 + jarvis/cogs/ctc2.py | 28 +---- jarvis/cogs/dbrand.py | 2 + jarvis/cogs/dev.py | 5 + jarvis/cogs/gl.py | 8 +- jarvis/cogs/image.py | 2 + jarvis/cogs/remindme.py | 52 ++++----- jarvis/cogs/rolegiver.py | 2 + jarvis/cogs/settings.py | 5 + jarvis/cogs/starboard.py | 3 + jarvis/cogs/twitter.py | 6 +- jarvis/cogs/util.py | 4 +- jarvis/cogs/verify.py | 2 + poetry.lock | 199 +++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 26 files changed, 349 insertions(+), 78 deletions(-) diff --git a/.flake8 b/.flake8 index 6e7cd96..50c8d97 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,7 @@ [flake8] extend-ignore = Q0, E501, C812, E203, W503, # These default to arguing with Black. We might configure some of them eventually + ANN002, ANN003, # Ignore *args, **kwargs ANN1, # Ignore self and cls annotations ANN204, ANN206, # return annotations for special methods and class methods D105, D107, # Missing Docstrings in magic method and __init__ diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 5c7e26c..425519e 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -4,22 +4,25 @@ from importlib.metadata import version as _v from dis_snek import Intents from jarvis_core.db import connect +from jarvis_core.log import get_logger from jarvis import utils from jarvis.client import Jarvis -from jarvis.config import get_config +from jarvis.config import JarvisConfig try: __version__ = _v("jarvis") except Exception: __version__ = "0.0.0" -jconfig = get_config() +jconfig = JarvisConfig.from_yaml() -logger = logging.getLogger("discord") -logger.setLevel(logging.getLevelName(jconfig.log_level)) +logger = get_logger("jarvis") +logger.setLevel(jconfig.log_level) file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") -file_handler.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s][%(name)s] %(message)s")) +file_handler.setFormatter( + logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s") +) logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES @@ -31,11 +34,17 @@ jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.s async def run() -> None: """Run J.A.R.V.I.S.""" + logger.info("Starting JARVIS") + logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") - jconfig.get_db_config() + logger.debug("Loading configuration from database") + # jconfig.get_db_config() + logger.debug("Loading extensions") for extension in utils.get_extensions(): jarvis.load_extension(extension) + logger.debug(f"Loaded {extension}") jarvis.max_messages = jconfig.max_messages + logger.debug("Running JARVIS") await jarvis.astart(jconfig.token) diff --git a/jarvis/client.py b/jarvis/client.py index c0ebc81..74e6020 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,4 +1,5 @@ """Custom JARVIS client.""" +import logging import re import traceback from datetime import datetime @@ -49,15 +50,19 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Snake): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 super().__init__(*args, **kwargs) + self.logger = logging.getLogger(__name__) self.phishing_domains = [] @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: + self.logger.debug("Updating phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/recent/86415") response.raise_for_status() data = await response.json() + self.logger.debug(f"Found {len(data)} changes to phishing domains") + for update in data: if update["type"] == "add": if update["domain"] not in self.phishing_domains: @@ -67,19 +72,21 @@ class Jarvis(Snake): self.phishing_domains.remove(update["domain"]) async def _sync_domains(self) -> None: + self.logger.debug("Loading phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: response = await session.get("https://phish.sinking.yachts/v2/all") response.raise_for_status() self.phishing_domains = await response.json() + self.logger.info(f"Protected from {len(self.phishing_domains)} phishing domains") @listen() async def on_ready(self) -> None: """Lepton on_ready override.""" await self._sync_domains() self._update_domains.start() - print("Logged in as {}".format(self.user)) # noqa: T001 - print("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 - print( # noqa: T001 + self.logger.info("Logged in as {}".format(self.user)) # noqa: T001 + self.logger.info("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 + self.logger.info( # noqa: T001 "https://discord.com/api/oauth2/authorize?client_id=" "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) @@ -418,7 +425,11 @@ class Jarvis(Snake): message = event.message modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) if modlog: - fields = [EmbedField("Original Message", message.content or "N/A", False)] + try: + content = message.content or "N/A" + except AttributeError: + content = "N/A" + fields = [EmbedField("Original Message", content, False)] if message.attachments: value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index c0c8050..6ca8447 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. BanCog.""" +import logging import re -from dis_snek import InteractionContext, Permissions +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -24,6 +25,10 @@ from jarvis.utils.permissions import admin_or_permissions class BanCog(ModcaseCog): """J.A.R.V.I.S. BanCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + async def discord_apply_ban( self, ctx: InteractionContext, diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 8a3a6b1..e00f096 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. KickCog.""" -from dis_snek import InteractionContext, Permissions +import logging + +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( @@ -18,6 +20,10 @@ from jarvis.utils.permissions import admin_or_permissions class KickCog(ModcaseCog): """J.A.R.V.I.S. KickCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="kick", description="Kick a user") @slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index eefdada..e1aeb61 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. LockCog.""" +import logging from typing import Union -from dis_snek import InteractionContext, Scale +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildText, GuildVoice from dis_snek.models.discord.enums import Permissions @@ -20,6 +21,10 @@ from jarvis.utils.permissions import admin_or_permissions class LockCog(Scale): """J.A.R.V.I.S. LockCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="lock", description="Lock a channel") @slash_option( name="reason", diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index 14407d8..e55b39e 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. LockdownCog.""" +import logging + from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import find_all, get from dis_snek.models.discord.channel import GuildCategory, GuildChannel @@ -93,6 +95,10 @@ async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: class LockdownCog(Scale): """J.A.R.V.I.S. LockdownCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command( name="lockdown", description="Manage server-wide lockdown", diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index ffb634f..a8c7666 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,7 +1,8 @@ """J.A.R.V.I.S. MuteCog.""" +import logging from datetime import datetime -from dis_snek import InteractionContext, Permissions +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( @@ -21,6 +22,10 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): """J.A.R.V.I.S. MuteCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 91d7caa..b8d6f4a 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. PurgeCog.""" +import logging + from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( @@ -18,6 +20,7 @@ class PurgeCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command(name="purge", description="Purge messages from channel") @slash_option( diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 38a3c87..9ef8152 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. RolepingCog.""" -from dis_snek import InteractionContext, Permissions, Scale +import logging + +from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -21,6 +23,10 @@ from jarvis.utils.permissions import admin_or_permissions class RolepingCog(Scale): """J.A.R.V.I.S. RolepingCog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command( name="roleping", description="Set up warnings for pinging specific roles", diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 86b08aa..3ef1a8a 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,5 +1,7 @@ """J.A.R.V.I.S. WarningCog.""" -from dis_snek import InteractionContext, Permissions +import logging + +from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -10,6 +12,7 @@ from dis_snek.models.snek.application_commands import ( slash_option, ) from dis_snek.models.snek.command import check +from jarvis_core.db import q from jarvis_core.db.models import Warning from jarvis.utils import build_embed @@ -21,6 +24,10 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(ModcaseCog): """J.A.R.V.I.S. WarningCog.""" + def __init__(self, bot: Snake): + super().__init__(bot) + self.logger = logging.getLogger(__name__) + @slash_command(name="warn", description="Warn a user") @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -72,8 +79,10 @@ class WarningCog(ModcaseCog): async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: warnings = ( await Warning.find( - user=user.id, - guild=ctx.guild.id, + q( + user=user.id, + guild=ctx.guild.id, + ) ) .sort("created_at", -1) .to_list(None) @@ -94,7 +103,7 @@ class WarningCog(ModcaseCog): else: fields = [] for warn in active_warns: - admin = await ctx.guild.get_member(warn.admin) + admin = await ctx.guild.fetch_member(warn.admin) admin_name = "||`[redacted]`||" if admin: admin_name = f"{admin.username}#{admin.discriminator}" @@ -144,6 +153,6 @@ class WarningCog(ModcaseCog): embed.set_thumbnail(url=ctx.guild.icon.url) pages.append(embed) - paginator = Paginator(bot=self.bot, *pages, timeout=300) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) await paginator.send(ctx) diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index b5caa19..40a5012 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Autoreact Cog.""" +import logging import re from typing import Optional, Tuple @@ -23,6 +24,7 @@ class AutoReactCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") async def create_autoreact( diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 67f9d20..8d4e71b 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -1,9 +1,9 @@ """J.A.R.V.I.S. Complete the Code 2 Cog.""" +import logging import re -from datetime import datetime, timedelta import aiohttp -from dis_snek import InteractionContext, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User @@ -14,7 +14,6 @@ from jarvis_core.db import q from jarvis_core.db.models import Guess from jarvis.utils import build_embed -from jarvis.utils.cachecog import CacheCog guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] @@ -25,11 +24,12 @@ invites = re.compile( ) -class CTCCog(CacheCog): +class CTCCog(Scale): """J.A.R.V.I.S. Complete the Code 2 Cog.""" def __init__(self, bot: Snake): - super().__init__(bot) + self.bot = bot + self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() self.url = "https://completethecodetwo.cards/pw" @@ -79,6 +79,7 @@ class CTCCog(CacheCog): if guessed: await ctx.send("Already guessed, dipshit.", ephemeral=True) return + result = await self._session.post(self.url, data=guess) correct = False if 200 <= result.status < 400: @@ -96,15 +97,6 @@ class CTCCog(CacheCog): ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: - exists = self.check_cache(ctx) - if exists: - await ctx.defer(ephemeral=True) - await ctx.send( - f"Please use existing interaction: {exists['paginator']._message.jump_url}", - ephemeral=True, - ) - return - guesses = Guess.objects().order_by("-correct", "-id") fields = [] for guess in guesses: @@ -141,14 +133,6 @@ class CTCCog(CacheCog): paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) - self.cache[hash(paginator)] = { - "guild": ctx.guild.id, - "user": ctx.author.id, - "timeout": datetime.utcnow() + timedelta(minutes=5), - "command": ctx.subcommand_name, - "paginator": paginator, - } - await paginator.send(ctx) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 9a14251..761ff12 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. dbrand cog.""" +import logging import re import aiohttp @@ -28,6 +29,7 @@ class DbrandCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self.base_url = "https://dbrand.com/" self._session = aiohttp.ClientSession() self._session.headers.update({"Content-Type": "application/json"}) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f867100..5d419e3 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -1,6 +1,7 @@ """J.A.R.V.I.S. Developer Cog.""" import base64 import hashlib +import logging import re import subprocess # noqa: S404 import uuid as uuidpy @@ -47,6 +48,10 @@ MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): """J.A.R.V.I.S. Developer Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="hash", description="Hash some data") @slash_option( name="method", diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index f8dbdfd..6bb92d0 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. GitLab Cog.""" import asyncio +import logging from datetime import datetime import gitlab @@ -17,7 +18,7 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets -from jarvis.config import get_config +from jarvis.config import JarvisConfig from jarvis.utils import build_embed guild_ids = [862402786116763668] @@ -28,7 +29,8 @@ class GitlabCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = get_config() + self.logger = logging.getLogger(__name__) + config = JarvisConfig.from_yaml() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) # J.A.R.V.I.S. GitLab ID is 29 self.project = self._gitlab.projects.get(29) @@ -473,5 +475,5 @@ class GitlabCog(Scale): def setup(bot: Snake) -> None: """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" - if get_config().gitlab_token: + if JarvisConfig.from_yaml().gitlab_token: GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index a9af120..f924d7f 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. image processing cog.""" +import logging import re from io import BytesIO @@ -28,6 +29,7 @@ class ImageCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() self.tgt_match = re.compile(r"([0-9]*\.?[0-9]*?) ?([KMGTP]?B?)", re.IGNORECASE) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 1b36042..728af87 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -1,10 +1,11 @@ """J.A.R.V.I.S. Remind Me Cog.""" import asyncio +import logging import re -from datetime import datetime, timedelta from typing import List from bson import ObjectId +from dateparser import parse from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel @@ -32,6 +33,10 @@ invites = re.compile( class RemindmeCog(Scale): """J.A.R.V.I.S. Remind Me Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + @slash_command(name="remindme", description="Set a reminder") @slash_option( name="private", @@ -44,6 +49,14 @@ class RemindmeCog(Scale): ctx: InteractionContext, private: bool = False, ) -> None: + reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))]) + if reminders >= 5: + await ctx.send( + "You already have 5 (or more) active reminders. " + "Please either remove an old one, or wait for one to pass", + ephemeral=True, + ) + return modal = Modal( title="Set your reminder!", components=[ @@ -56,12 +69,13 @@ class RemindmeCog(Scale): ), InputText( label="When to remind you?", - placeholder="1h 30m", + placeholder="1h 30m | in 5 minutes | November 11, 4011", style=TextStyles.SHORT, custom_id="delay", ), ], ) + await ctx.send_modal(modal) try: response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) @@ -82,33 +96,19 @@ class RemindmeCog(Scale): await ctx.send("Hey, you should probably make this readable", ephemeral=True) return - units = {"w": "weeks", "d": "days", "h": "hours", "m": "minutes", "s": "seconds"} - delta = {"weeks": 0, "days": 0, "hours": 0, "minutes": 0, "seconds": 0} - - if times := time_pattern.findall(delay): - for t in times: - delta[units[t[-1]]] += float(t[:-1]) - else: - await ctx.send( - "Invalid time string, please follow example: `1w 3d 7h 5m 20s`", ephemeral=True + settings = { + "PREFER_DATES_FROM": "future", + "TIMEZONE": "UTC", + "RETURN_AS_TIMEZONE_AWARE": False, + } + remind_at = parse(delay, settings=settings) + if not remind_at: + self.logger.debug(f"Failed to parse delay: {delay}") + await response.send( + f"`{delay}` is not a parsable date, please try again", ephemeral=True ) return - if not any(value for value in delta.items()): - await ctx.send("At least one time period is required", ephemeral=True) - return - - reminders = len([x async for x in Reminder.find(q(user=ctx.author.id, active=True))]) - if reminders >= 5: - await ctx.send( - "You already have 5 (or more) active reminders. " - "Please either remove an old one, or wait for one to pass", - ephemeral=True, - ) - return - - remind_at = datetime.utcnow() + timedelta(**delta) - r = Reminder( user=ctx.author.id, channel=ctx.channel.id, diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index ea6b148..7269f3d 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Role Giver Cog.""" import asyncio +import logging from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import get @@ -25,6 +26,7 @@ class RolegiverCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command( name="rolegiver", diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index fec3ee6..29afb7d 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Settings Management Cog.""" +import logging from typing import Any from dis_snek import InteractionContext, Scale, Snake @@ -25,6 +26,10 @@ from jarvis.utils.permissions import admin_or_permissions class SettingsCog(Scale): """J.A.R.V.I.S. Settings Management Cog.""" + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + async def update_settings(self, setting: str, value: Any, guild: int) -> bool: """Update a guild setting.""" existing = await Setting.find_one(q(setting=setting, guild=guild)) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 871e25c..b74afb4 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. Starboard Cog.""" +import logging + from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText @@ -32,6 +34,7 @@ class StarboardCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command( name="starboard", diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 79521a0..aec012f 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Twitter Cog.""" import asyncio +import logging import tweepy from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -15,7 +16,7 @@ from dis_snek.models.snek.command import check from jarvis_core.db import q from jarvis_core.db.models import TwitterAccount, TwitterFollow -from jarvis import jconfig +from jarvis.config import JarvisConfig from jarvis.utils.permissions import admin_or_permissions @@ -24,7 +25,8 @@ class TwitterCog(Scale): def __init__(self, bot: Snake): self.bot = bot - config = jconfig + self.logger = logging.getLogger(__name__) + config = JarvisConfig.from_yaml() auth = tweepy.AppAuthHandler( config.twitter["consumer_key"], config.twitter["consumer_secret"] ) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index aaa8d53..7e195ef 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,4 +1,5 @@ """J.A.R.V.I.S. Utility Cog.""" +import logging import platform import re import secrets @@ -24,7 +25,6 @@ from dis_snek.models.snek.cooldowns import Buckets from PIL import Image import jarvis -from jarvis.config import get_config from jarvis.data import pigpen from jarvis.data.robotcamo import emotes, hk, names from jarvis.utils import build_embed, get_repo_hash @@ -41,7 +41,7 @@ class UtilCog(Scale): def __init__(self, bot: Snake): self.bot = bot - self.config = get_config() + self.logger = logging.getLogger(__name__) @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index a4b0647..f281d97 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -1,5 +1,6 @@ """J.A.R.V.I.S. Verify Cog.""" import asyncio +import logging from random import randint from dis_snek import InteractionContext, Scale, Snake @@ -34,6 +35,7 @@ class VerifyCog(Scale): def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) @slash_command(name="verify", description="Verify that you've read the rules") @cooldown(bucket=Buckets.USER, rate=1, interval=15) diff --git a/poetry.lock b/poetry.lock index cf974fc..c50d2db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,6 +134,25 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "dateparser" +version = "1.1.1" +description = "Date parsing library designed to parse dates from HTML pages" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +python-dateutil = "*" +pytz = "*" +regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27,<2022.3.15" +tzlocal = "*" + +[package.extras] +calendars = ["convertdate", "hijri-converter", "convertdate"] +fasttext = ["fasttext"] +langdetect = ["langdetect"] + [[package]] name = "dis-snek" version = "7.0.0" @@ -226,7 +245,7 @@ plugins = ["setuptools"] [[package]] name = "jarvis-core" -version = "0.6.1" +version = "0.7.0" description = "" category = "main" optional = false @@ -244,7 +263,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "52a3d568030a79db8ad5ddf65c26216913598bf5" +resolved_reference = "b81ea66d12b1a32c8291cbe9e14fe17b8e020d08" [[package]] name = "jedi" @@ -560,6 +579,17 @@ python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-gitlab" version = "3.2.0" @@ -626,6 +656,25 @@ rope = ["rope (>0.10.5)"] test = ["pylint (>=2.5.0)", "pytest", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5", "flaky"] yapf = ["yapf"] +[[package]] +name = "pytz" +version = "2022.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pytz-deprecation-shim" +version = "0.1.0.post0" +description = "Shims to make deprecation of pytz easier" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +tzdata = {version = "*", markers = "python_version >= \"3.6\""} + [[package]] name = "pyyaml" version = "6.0" @@ -634,6 +683,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "regex" +version = "2022.3.2" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "requests" version = "2.27.1" @@ -689,6 +746,14 @@ python-versions = "*" [package.extras] dev = ["build", "pytest", "pytest-timeout"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "smmap" version = "5.0.0" @@ -748,6 +813,30 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "tzdata" +version = "2022.1" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + +[[package]] +name = "tzlocal" +version = "4.1" +description = "tzinfo object for the local timezone" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz-deprecation-shim = "*" +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] +test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] + [[package]] name = "ujson" version = "5.1.0" @@ -825,7 +914,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "2adcfd60566d51e43a6f5a3ee0f96140a38e91401800916042cc7cd7e6adb37d" +content-hash = "ce783f7855bb480b335731403679dc8249644570965a4cff6091eb377c5472df" [metadata.files] aiohttp = [ @@ -963,6 +1052,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +dateparser = [ + {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, + {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, +] dis-snek = [ {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, @@ -1502,6 +1595,10 @@ pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] python-gitlab = [ {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, @@ -1514,6 +1611,14 @@ python-lsp-server = [ {file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"}, {file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"}, ] +pytz = [ + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, +] +pytz-deprecation-shim = [ + {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, + {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, +] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1549,6 +1654,82 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] +regex = [ + {file = "regex-2022.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab69b4fe09e296261377d209068d52402fb85ef89dc78a9ac4a29a895f4e24a7"}, + {file = "regex-2022.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5bc5f921be39ccb65fdda741e04b2555917a4bced24b4df14eddc7569be3b493"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43eba5c46208deedec833663201752e865feddc840433285fbadee07b84b464d"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c68d2c04f7701a418ec2e5631b7f3552efc32f6bcc1739369c6eeb1af55f62e0"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:caa2734ada16a44ae57b229d45091f06e30a9a52ace76d7574546ab23008c635"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef806f684f17dbd6263d72a54ad4073af42b42effa3eb42b877e750c24c76f86"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be319f4eb400ee567b722e9ea63d5b2bb31464e3cf1b016502e3ee2de4f86f5c"}, + {file = "regex-2022.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:42bb37e2b2d25d958c25903f6125a41aaaa1ed49ca62c103331f24b8a459142f"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:91e0f7e7be77250b808a5f46d90bf0032527d3c032b2131b63dee54753a4d729"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cb3652bbe6720786b9137862205986f3ae54a09dec8499a995ed58292bdf77c2"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:878c626cbca3b649e14e972c14539a01191d79e58934e3f3ef4a9e17f90277f8"}, + {file = "regex-2022.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6df070a986fc064d865c381aecf0aaff914178fdf6874da2f2387e82d93cc5bd"}, + {file = "regex-2022.3.2-cp310-cp310-win32.whl", hash = "sha256:b549d851f91a4efb3e65498bd4249b1447ab6035a9972f7fc215eb1f59328834"}, + {file = "regex-2022.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:8babb2b5751105dc0aef2a2e539f4ba391e738c62038d8cb331c710f6b0f3da7"}, + {file = "regex-2022.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1977bb64264815d3ef016625adc9df90e6d0e27e76260280c63eca993e3f455f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e73652057473ad3e6934944af090852a02590c349357b79182c1b681da2c772"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b22ff939a8856a44f4822da38ef4868bd3a9ade22bb6d9062b36957c850e404f"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:878f5d649ba1db9f52cc4ef491f7dba2d061cdc48dd444c54260eebc0b1729b9"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0008650041531d0eadecc96a73d37c2dc4821cf51b0766e374cb4f1ddc4e1c14"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06b1df01cf2aef3a9790858af524ae2588762c8a90e784ba00d003f045306204"}, + {file = "regex-2022.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57484d39447f94967e83e56db1b1108c68918c44ab519b8ecfc34b790ca52bf7"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74d86e8924835f863c34e646392ef39039405f6ce52956d8af16497af4064a30"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:ae17fc8103f3b63345709d3e9654a274eee1c6072592aec32b026efd401931d0"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5f92a7cdc6a0ae2abd184e8dfd6ef2279989d24c85d2c85d0423206284103ede"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:5dcc4168536c8f68654f014a3db49b6b4a26b226f735708be2054314ed4964f4"}, + {file = "regex-2022.3.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1e30762ddddb22f7f14c4f59c34d3addabc789216d813b0f3e2788d7bcf0cf29"}, + {file = "regex-2022.3.2-cp36-cp36m-win32.whl", hash = "sha256:286ff9ec2709d56ae7517040be0d6c502642517ce9937ab6d89b1e7d0904f863"}, + {file = "regex-2022.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d326ff80ed531bf2507cba93011c30fff2dd51454c85f55df0f59f2030b1687b"}, + {file = "regex-2022.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9d828c5987d543d052b53c579a01a52d96b86f937b1777bbfe11ef2728929357"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87ac58b9baaf50b6c1b81a18d20eda7e2883aa9a4fb4f1ca70f2e443bfcdc57"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6c2441538e4fadd4291c8420853431a229fcbefc1bf521810fbc2629d8ae8c2"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3356afbb301ec34a500b8ba8b47cba0b44ed4641c306e1dd981a08b416170b5"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d96eec8550fd2fd26f8e675f6d8b61b159482ad8ffa26991b894ed5ee19038b"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf668f26604e9f7aee9f8eaae4ca07a948168af90b96be97a4b7fa902a6d2ac1"}, + {file = "regex-2022.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb0e2845e81bdea92b8281a3969632686502565abf4a0b9e4ab1471c863d8f3"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:87bc01226cd288f0bd9a4f9f07bf6827134dc97a96c22e2d28628e824c8de231"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:09b4b6ccc61d4119342b26246ddd5a04accdeebe36bdfe865ad87a0784efd77f"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:9557545c10d52c845f270b665b52a6a972884725aa5cf12777374e18f2ea8960"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:0be0c34a39e5d04a62fd5342f0886d0e57592a4f4993b3f9d257c1f688b19737"}, + {file = "regex-2022.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7b103dffb9f6a47ed7ffdf352b78cfe058b1777617371226c1894e1be443afec"}, + {file = "regex-2022.3.2-cp37-cp37m-win32.whl", hash = "sha256:f8169ec628880bdbca67082a9196e2106060a4a5cbd486ac51881a4df805a36f"}, + {file = "regex-2022.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b9c16a807b17b17c4fa3a1d8c242467237be67ba92ad24ff51425329e7ae3d0"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67250b36edfa714ba62dc62d3f238e86db1065fccb538278804790f578253640"}, + {file = "regex-2022.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5510932596a0f33399b7fff1bd61c59c977f2b8ee987b36539ba97eb3513584a"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f7ee2289176cb1d2c59a24f50900f8b9580259fa9f1a739432242e7d254f93"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d7a68fa53688e1f612c3246044157117403c7ce19ebab7d02daf45bd63913e"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf5317c961d93c1a200b9370fb1c6b6836cc7144fef3e5a951326912bf1f5a3"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad397bc7d51d69cb07ef89e44243f971a04ce1dca9bf24c992c362406c0c6573"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:297c42ede2c81f0cb6f34ea60b5cf6dc965d97fa6936c11fc3286019231f0d66"}, + {file = "regex-2022.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af4d8cc28e4c7a2f6a9fed544228c567340f8258b6d7ea815b62a72817bbd178"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:452519bc4c973e961b1620c815ea6dd8944a12d68e71002be5a7aff0a8361571"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cb34c2d66355fb70ae47b5595aafd7218e59bb9c00ad8cc3abd1406ca5874f07"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d146e5591cb67c5e836229a04723a30af795ef9b70a0bbd913572e14b7b940f"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:03299b0bcaa7824eb7c0ebd7ef1e3663302d1b533653bfe9dc7e595d453e2ae9"}, + {file = "regex-2022.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ccb0a4ab926016867260c24c192d9df9586e834f5db83dfa2c8fffb3a6e5056"}, + {file = "regex-2022.3.2-cp38-cp38-win32.whl", hash = "sha256:f7e8f1ee28e0a05831c92dc1c0c1c94af5289963b7cf09eca5b5e3ce4f8c91b0"}, + {file = "regex-2022.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35ed2f3c918a00b109157428abfc4e8d1ffabc37c8f9abc5939ebd1e95dabc47"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:55820bc631684172b9b56a991d217ec7c2e580d956591dc2144985113980f5a3"}, + {file = "regex-2022.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:83f03f0bd88c12e63ca2d024adeee75234d69808b341e88343b0232329e1f1a1"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42d6007722d46bd2c95cce700181570b56edc0dcbadbfe7855ec26c3f2d7e008"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:320c2f4106962ecea0f33d8d31b985d3c185757c49c1fb735501515f963715ed"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e51ad1e6131c496b58d317bc9abec71f44eb1957d32629d06013a21bc99cac"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bc3a5effa5974be6d965ed8301ac1e869bc18425c8a8fac179fbe7876e3aee"}, + {file = "regex-2022.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e5602a9b5074dcacc113bba4d2f011d2748f50e3201c8139ac5b68cf2a76bd8b"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:729aa8ca624c42f309397c5fc9e21db90bf7e2fdd872461aabdbada33de9063c"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d6ecfd1970b3380a569d7b3ecc5dd70dba295897418ed9e31ec3c16a5ab099a5"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:13bbf0c9453c6d16e5867bda7f6c0c7cff1decf96c5498318bb87f8136d2abd4"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:58ba41e462653eaf68fc4a84ec4d350b26a98d030be1ab24aba1adcc78ffe447"}, + {file = "regex-2022.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c0446b2871335d5a5e9fcf1462f954586b09a845832263db95059dcd01442015"}, + {file = "regex-2022.3.2-cp39-cp39-win32.whl", hash = "sha256:20e6a27959f162f979165e496add0d7d56d7038237092d1aba20b46de79158f1"}, + {file = "regex-2022.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9efa41d1527b366c88f265a227b20bcec65bda879962e3fc8a2aee11e81266d7"}, + {file = "regex-2022.3.2.tar.gz", hash = "sha256:79e5af1ff258bc0fe0bdd6f69bc4ae33935a898e3cbefbbccf22e88a27fa053b"}, +] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, @@ -1565,6 +1746,10 @@ rope = [ {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, ] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -1589,6 +1774,14 @@ typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] +tzdata = [ + {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, + {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, +] +tzlocal = [ + {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, + {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, +] ujson = [ {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, {file = "ujson-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0cae4a9c141856f7ad1a79c17ff1aaebf7fd8faa2f2c2614c37d6f82ed261d96"}, diff --git a/pyproject.toml b/pyproject.toml index d63854d..110df1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ orjson = "^3.6.6" jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git", rev = "main"} aiohttp = "^3.8.1" pastypy = "^1.0.1" +dateparser = "^1.1.1" [tool.poetry.dev-dependencies] python-lsp-server = {extras = ["all"], version = "^1.3.3"} From 1225034f72a948636ed11ea3612fa886d82e743e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 19:04:31 -0600 Subject: [PATCH 119/365] More logging --- jarvis/client.py | 7 +++++-- jarvis/cogs/admin/warning.py | 3 +++ jarvis/cogs/dev.py | 3 ++- jarvis/cogs/remindme.py | 23 +++++++++++++++++++++++ jarvis/cogs/verify.py | 2 ++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 74e6020..9f99005 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -407,7 +407,7 @@ class Jarvis(Snake): url=after.jump_url, ) embed.set_footer( - text=f"{after.author.user.username}#{after.author.discriminator} | {after.author.id}" + text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" ) await channel.send(embed=embed) if not isinstance(after.channel, DMChannel) and not after.author.bot: @@ -475,6 +475,9 @@ class Jarvis(Snake): url=message.jump_url, ) embed.set_footer( - text=f"{message.author.user.username}#{message.author.discriminator} | {message.author.id}" + text=( + f"{message.author.username}#{message.author.discriminator} | " + f"{message.author.id}" + ) ) await channel.send(embed=embed) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 3ef1a8a..0c15964 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -87,6 +87,9 @@ class WarningCog(ModcaseCog): .sort("created_at", -1) .to_list(None) ) + if len(warnings) == 0: + await ctx.send("That user has no warnings.", ephemeral=True) + return active_warns = get_all(warnings, active=True) pages = [] diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 5d419e3..946e374 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -88,8 +88,9 @@ class DevCog(Scale): title = attach.filename elif url.match(data): try: - if await get_size(data) > MAX_FILESIZE: + if (size := await get_size(data)) > MAX_FILESIZE: await ctx.send("Please hash files that are <= 5GB in size", ephemeral=True) + self.logger.debug(f"Refused to hash file of size {convert_bytesize(size)}") return except Exception as e: await ctx.send(f"Failed to retrieve URL: ```\n{e}\n```", ephemeral=True) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 728af87..c5bcd21 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -277,6 +277,29 @@ class RemindmeCog(Scale): component.disabled = True await message.edit(components=components) + @slash_command( + name="reminders", + sub_cmd_name="fetch", + sub_cmd_description="Fetch a reminder that failed to send", + ) + @slash_option( + name="id", description="ID of the reminder", opt_type=OptionTypes.STRING, required=True + ) + async def _fetch(self, ctx: InteractionContext, id: str) -> None: + reminder = await Reminder.find_one(q(id=id)) + if not reminder: + await ctx.send(f"Reminder {id} does not exist") + return + + embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) + embed.set_author( + name=ctx.author.display_name + "#" + ctx.author.discriminator, + icon_url=ctx.author.display_avatar, + ) + + embed.set_thumbnail(url=ctx.author.display_avatar) + await ctx.send(embed=embed, ephemeral=reminder.private) + def setup(bot: Snake) -> None: """Add RemindmeCog to J.A.R.V.I.S.""" diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index f281d97..05a723d 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -81,6 +81,7 @@ class VerifyCog(Scale): components=components, ) await response.context.message.delete(delay=5) + self.logger.debug(f"User {ctx.author.id} verified successfully") else: await response.context.edit_origin( content=( @@ -90,6 +91,7 @@ class VerifyCog(Scale): ) except asyncio.TimeoutError: await message.delete(delay=30) + self.logger.debug(f"User {ctx.author.id} failed to verify before timeout") def setup(bot: Snake) -> None: From 3b12ffa7f0d4153479156683e48a2d88a800ce52 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 23 Mar 2022 20:30:57 -0600 Subject: [PATCH 120/365] Fix errors not closing modal --- jarvis/cogs/remindme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index c5bcd21..9c14804 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -84,16 +84,16 @@ class RemindmeCog(Scale): except asyncio.TimeoutError: return if len(message) > 500: - await ctx.send("Reminder cannot be > 500 characters.", ephemeral=True) + await response.send("Reminder cannot be > 500 characters.", ephemeral=True) return elif invites.search(message): - await ctx.send( + await response.send( "Listen, don't use this to try and bypass the rules", ephemeral=True, ) return elif not valid.fullmatch(message): - await ctx.send("Hey, you should probably make this readable", ephemeral=True) + await response.send("Hey, you should probably make this readable", ephemeral=True) return settings = { From dffd5f780ff5554834518c05402880cdf6e1d834 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:45:41 -0600 Subject: [PATCH 121/365] Cooldown handling, formatting --- .flake8 | 1 + jarvis/client.py | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index 50c8d97..36c40f6 100644 --- a/.flake8 +++ b/.flake8 @@ -11,5 +11,6 @@ extend-ignore = D101, # Missing docstring in public class # Plugins we don't currently include: flake8-return + R502, # do not implicitly return None in function able to return non-None value. R503, # missing explicit return at the end of function ableto return non-None value. max-line-length=100 diff --git a/jarvis/client.py b/jarvis/client.py index 9f99005..4619f84 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -7,6 +7,7 @@ from datetime import datetime from aiohttp import ClientSession from dis_snek import Snake, listen from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField @@ -95,14 +96,23 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" + if isinstance(error, CommandOnCooldown): + await ctx.send(str(error), ephemeral=True) + return + elif isinstance(error, CommandCheckFailure): + await ctx.send("I'm afraid I can't let you do that", ephemeral=True) + return guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") timestamp = int(datetime.now().timestamp()) timestamp = f"" - arg_str = ( - "\n".join(f" {k}: {v}" for k, v in ctx.kwargs.items()) if ctx.kwargs else " None" - ) + arg_str = "" + for k, v in ctx.kwargs.items(): + arg_str += f" {k}: " + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f"{v}\n" callback_args = "\n".join(f" - {i}" for i in args) if args else " None" callback_kwargs = ( "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" @@ -141,7 +151,14 @@ class Jarvis(Snake): modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) - args = " ".join(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}" for k, v in ctx.kwargs.items()) + args = [] + for k, v in ctx.kwargs.items(): + if isinstance(v, str): + v = v.replace("`", "\\`") + if len(v) > 100: + v = v[:97] + "..." + args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + args = " ".join(args) fields = [ EmbedField( name="Command", From 58e0ee892b2e803075efc382171d34adc6227c5d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:46:47 -0600 Subject: [PATCH 122/365] Formatting changes, delay changes --- jarvis/cogs/admin/ban.py | 25 +++++++++++++------------ jarvis/cogs/verify.py | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 6ca8447..0f18c7d 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -62,9 +62,9 @@ class BanCog(ModcaseCog): embed.set_author( name=user.display_name, - icon_url=user.avatar, + icon_url=user.avatar.url, ) - embed.set_thumbnail(url=user.avatar) + embed.set_thumbnail(url=user.avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) @@ -89,9 +89,9 @@ class BanCog(ModcaseCog): ) embed.set_author( name=user.username, - icon_url=user.avatar, + icon_url=user.avatar.url, ) - embed.set_thumbnail(url=user.avatar) + embed.set_thumbnail(url=user.avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embed=embed) @@ -121,15 +121,15 @@ class BanCog(ModcaseCog): async def _ban( self, ctx: InteractionContext, + user: User, reason: str, - user: User = None, btype: str = "perm", duration: int = 4, ) -> None: - if not user or user == ctx.author: + if user.id == ctx.author.id: await ctx.send("You cannot ban yourself.", ephemeral=True) return - if user == self.bot.user: + if user.id == self.bot.user.id: await ctx.send("I'm afraid I can't let you do that", ephemeral=True) return if btype == "temp" and duration < 0: @@ -215,9 +215,10 @@ class BanCog(ModcaseCog): discord_ban_info = None database_ban_info = None - bans = await ctx.guild.bans() + bans = await ctx.guild.fetch_bans() # Try to get ban information out of Discord + self.logger.debug(f"{user}") if re.match(r"^[0-9]{1,}$", user): # User ID user = int(user) discord_ban_info = find(lambda x: x.user.id == user, bans) @@ -252,9 +253,9 @@ class BanCog(ModcaseCog): # try to find the relevant information in the database. # We take advantage of the previous checks to save CPU cycles if not discord_ban_info: - if isinstance(user, int): + if isinstance(user, User): database_ban_info = await Ban.find_one( - q(guild=ctx.guild.id, user=user, active=True) + q(guild=ctx.guild.id, user=user.id, active=True) ) else: search = { @@ -320,10 +321,10 @@ class BanCog(ModcaseCog): search["active"] = True if btype > 0: search["type"] = types[btype] - bans = Ban.find(search).sort([("created_at", -1)]) + bans = await Ban.find(search).sort([("created_at", -1)]).to_list(None) db_bans = [] fields = [] - async for ban in bans: + for ban in bans: if not ban.username: user = await self.bot.fetch_user(ban.user) ban.username = user.username if user else "[deleted user]" diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 05a723d..f3cbe29 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -38,7 +38,7 @@ class VerifyCog(Scale): self.logger = logging.getLogger(__name__) @slash_command(name="verify", description="Verify that you've read the rules") - @cooldown(bucket=Buckets.USER, rate=1, interval=15) + @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _verify(self, ctx: InteractionContext) -> None: await ctx.defer() role = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) @@ -90,7 +90,7 @@ class VerifyCog(Scale): ) ) except asyncio.TimeoutError: - await message.delete(delay=30) + await message.delete(delay=2) self.logger.debug(f"User {ctx.author.id} failed to verify before timeout") From a6252e262c0a56342890e47c1400030c2f983115 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:47:24 -0600 Subject: [PATCH 123/365] Catch failed DM to user --- jarvis/cogs/admin/kick.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index e00f096..4175987 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -59,7 +59,11 @@ class KickCog(ModcaseCog): await user.send(embed=embed) except Exception: send_failed = True - await ctx.guild.kick(user, reason=reason) + try: + await ctx.guild.kick(user, reason=reason) + except Exception as e: + await ctx.send(f"Failed to kick user:\n```\n{e}\n```", ephemeral=True) + return fields = [EmbedField(name="DM Sent?", value=str(not send_failed))] embed = build_embed( From f1f19366738ccba582d6490a61f2a0cde965e37e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:47:35 -0600 Subject: [PATCH 124/365] Merge all perms into one for simplicity sake --- jarvis/cogs/admin/lock.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index e1aeb61..7c8f4a2 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -67,11 +67,7 @@ class LockCog(Scale): if not channel: channel = ctx.channel - # role = ctx.guild.default_role # Uncomment once implemented - if isinstance(channel, GuildText): - to_deny = Permissions.SEND_MESSAGES - elif isinstance(channel, GuildVoice): - to_deny = Permissions.CONNECT | Permissions.SPEAK + to_deny = Permissions.CONNECT | Permissions.SPEAK | Permissions.SEND_MESSAGES current = get(channel.permission_overwrites, id=ctx.guild.id) if current: From 9f65213ea6cffaf72b01635cb30b37db1b34de0d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:03 -0600 Subject: [PATCH 125/365] URL fixes, catches for empty/non-existent data --- jarvis/cogs/util.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 7e195ef..61b3da2 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -13,7 +13,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.role import Role -from dis_snek.models.discord.user import User +from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommandChoice, @@ -54,6 +54,12 @@ class UtilCog(Scale): fields.append(EmbedField(name="dis-snek", value=const.__version__)) fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) + num_domains = len(self.bot.phishing_domains) + fields.append( + EmbedField( + name="Phishing Protection", value=f"Detecting {num_domains} phishing domains" + ) + ) embed = build_embed(title=title, description=desc, fields=fields, color=color) await ctx.send(embed=embed) @@ -96,6 +102,8 @@ class UtilCog(Scale): to_send += f":{names[id]}:" if len(to_send) > 2000: await ctx.send("Too long.", ephemeral=True) + elif len(to_send) == 0: + await ctx.send("No valid text found", ephemeral=True) else: await ctx.send(to_send) @@ -111,7 +119,10 @@ class UtilCog(Scale): if not user: user = ctx.author - avatar = user.display_avatar.url + avatar = user.avatar.url + if isinstance(user, Member): + avatar = user.display_avatar.url + embed = build_embed(title="Avatar", description="", fields=[], color="#00FFEE") embed.set_image(url=avatar) embed.set_author(name=f"{user.username}#{user.discriminator}", icon_url=avatar) @@ -175,8 +186,12 @@ class UtilCog(Scale): required=False, ) async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: + await ctx.defer() if not user: user = ctx.author + if not await ctx.guild.fetch_member(user.id): + await ctx.send("That user isn't in this guild.", ephemeral=True) + return user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) From 0729bce29564a501585c3119488fca3d78716bcd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:20 -0600 Subject: [PATCH 126/365] Better date parsing to be future-only --- jarvis/cogs/remindme.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9c14804..05e6d81 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,10 +2,12 @@ import asyncio import logging import re +from datetime import datetime from typing import List from bson import ObjectId from dateparser import parse +from dateparser_data.settings import default_parsers from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import GuildChannel @@ -96,12 +98,27 @@ class RemindmeCog(Scale): await response.send("Hey, you should probably make this readable", ephemeral=True) return - settings = { + base_settings = { "PREFER_DATES_FROM": "future", "TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": False, } - remind_at = parse(delay, settings=settings) + rt_settings = base_settings.copy() + rt_settings["PARSERS"] = [ + x for x in default_parsers if x not in ["absolute-time", "timestamp"] + ] + + rt_remind_at = parse(delay, settings=rt_settings) + + at_settings = base_settings.copy() + at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] + at_remind_at = parse(delay, settings=at_settings) + + if rt_remind_at and at_remind_at: + remind_at = max(rt_remind_at, at_remind_at) + else: + remind_at = rt_remind_at or at_remind_at + if not remind_at: self.logger.debug(f"Failed to parse delay: {delay}") await response.send( @@ -109,6 +126,15 @@ class RemindmeCog(Scale): ) return + if remind_at < datetime.utcnow(): + await response.send( + f"`{delay}` is in the past. Past reminders aren't allowed", ephemeral=True + ) + return + + elif remind_at < datetime.utcnow(): + pass + r = Reminder( user=ctx.author.id, channel=ctx.channel.id, From 02d50180f12cf80fd9126ce7fd96a10124457679 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:33 -0600 Subject: [PATCH 127/365] Verify that target sizes are sane --- jarvis/cogs/image.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index f924d7f..d15fdd8 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -58,6 +58,7 @@ class ImageCog(Scale): async def _resize( self, ctx: InteractionContext, target: str, attachment: Attachment = None, url: str = None ) -> None: + await ctx.defer() if not attachment and not url: await ctx.send("A URL or attachment is required", ephemeral=True) return @@ -73,10 +74,18 @@ class ImageCog(Scale): ) return - tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) + try: + tgt_size = unconvert_bytesize(float(tgt.groups()[0]), tgt.groups()[1]) + except ValueError: + await ctx.send("Failed to read your target size. Try a more sane one", ephemeral=True) + return + if tgt_size > unconvert_bytesize(8, "MB"): await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True) return + elif tgt_size < 1024: + await ctx.send("Sizes < 1KB are extremely unreliable and are disabled", ephemeral=True) + return if attachment: url = attachment.url @@ -91,7 +100,7 @@ class ImageCog(Scale): size = len(data) if size <= tgt_size: - await ctx.send("Image already meets target.") + await ctx.send("Image already meets target.", ephemeral=True) return ratio = max(tgt_size / size - 0.02, 0.50) From 38b86d46943b71c1a10aa6ec413c0791a0b02f41 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:44 -0600 Subject: [PATCH 128/365] Catch encoding errors --- jarvis/cogs/dev.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 946e374..f746d23 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -207,7 +207,11 @@ class DevCog(Scale): async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "encode") - encoded = method(data.encode("UTF-8")).decode("UTF-8") + try: + encoded = method(data.encode("UTF-8")).decode("UTF-8") + except Exception as e: + await ctx.send(f"Failed to encode data: {e}") + return fields = [ EmbedField(name="Plaintext", value=f"`{data}`", inline=False), EmbedField(name=mstr, value=f"`{encoded}`", inline=False), @@ -232,7 +236,11 @@ class DevCog(Scale): async def _decode(self, ctx: InteractionContext, method: str, data: str) -> None: mstr = method method = getattr(base64, method + "decode") - decoded = method(data.encode("UTF-8")).decode("UTF-8") + try: + decoded = method(data.encode("UTF-8")).decode("UTF-8") + except Exception as e: + await ctx.send(f"Failed to decode data: {e}") + return if invites.search(decoded): await ctx.send( "Please don't use this to bypass invite restrictions", From 0c97e8096a6bc6d85108e3d36f7aeefac063f959 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 25 Mar 2022 12:48:51 -0600 Subject: [PATCH 129/365] Delete invalid rolepings --- jarvis/cogs/admin/roleping.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 9ef8152..47c0291 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -75,6 +75,9 @@ class RolepingCog(Scale): embeds = [] for roleping in rolepings: role = await ctx.guild.fetch_role(roleping.role) + if not role: + await roleping.delete() + continue broles = find_all(lambda x: x.id in roleping.bypass["roles"], ctx.guild.roles) bypass_roles = [r.mention or "||`[redacted]`||" for r in broles] bypass_users = [ From 5d3af23f0ef48f7685eeb62785e2cd1d2d686cc1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:27:26 -0600 Subject: [PATCH 130/365] Fix timestamps being wrong --- jarvis/client.py | 11 ++++++----- jarvis/cogs/admin/mute.py | 14 ++++++++++---- jarvis/cogs/remindme.py | 6 +++--- jarvis/utils/cogs.py | 4 ++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 4619f84..04d44bb 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -2,7 +2,7 @@ import logging import re import traceback -from datetime import datetime +from datetime import datetime, timezone from aiohttp import ClientSession from dis_snek import Snake, listen @@ -104,7 +104,7 @@ class Jarvis(Snake): return guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) - error_time = datetime.utcnow().strftime("%d-%m-%Y %H:%M-%S.%f UTC") + error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC") timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" @@ -124,18 +124,19 @@ class Jarvis(Snake): callback_args=callback_args, callback_kwargs=callback_kwargs, ) - if len(full_message) >= 1900: - error_message = " ".join(traceback.format_exception(error)) + error_message = "".join(traceback.format_exception(error)) + if len(full_message + error_message) >= 1800: + error_message = " ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message paste = Paste(content=full_message) await paste.save(DEFAULT_SITE) + self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." f"\nPlease see log at {paste.url}" ) else: - error_message = "".join(traceback.format_exception(error)) await channel.send( f"JARVIS encountered an error at {timestamp}:" f"\n```yaml\n{full_message}\n```" diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index a8c7666..4ccf7f1 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,6 +1,6 @@ """J.A.R.V.I.S. MuteCog.""" import logging -from datetime import datetime +from datetime import datetime, timedelta, timezone from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField @@ -76,7 +76,8 @@ class MuteCog(ModcaseCog): await ctx.send("Mute must be less than 4 weeks (2419200 seconds)", ephemeral=True) return - await user.timeout(communication_disabled_until=duration, reason=reason) + until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) + await user.timeout(communication_disabled_until=until, reason=reason) m = Mute( user=user.id, reason=reason, @@ -86,11 +87,15 @@ class MuteCog(ModcaseCog): active=True, ) await m.commit() + ts = int(until.timestamp()) embed = build_embed( title="User Muted", description=f"{user.mention} has been muted", - fields=[EmbedField(name="Reason", value=reason)], + fields=[ + EmbedField(name="Reason", value=reason), + EmbedField(name="Until", value=f" "), + ], ) embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) embed.set_thumbnail(url=user.display_avatar.url) @@ -109,7 +114,8 @@ class MuteCog(ModcaseCog): async def _unmute(self, ctx: InteractionContext, user: Member) -> None: if ( not user.communication_disabled_until - or user.communication_disabled_until < datetime.now() # noqa: W503 + or user.communication_disabled_until.timestamp() + < datetime.now(tz=timezone.utc).timestamp() # noqa: W503 ): await ctx.send("User is not muted", ephemeral=True) return diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 05e6d81..b03271b 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -2,7 +2,7 @@ import asyncio import logging import re -from datetime import datetime +from datetime import datetime, timezone from typing import List from bson import ObjectId @@ -126,13 +126,13 @@ class RemindmeCog(Scale): ) return - if remind_at < datetime.utcnow(): + if remind_at < datetime.now(tz=timezone.utc): await response.send( f"`{delay}` is in the past. Past reminders aren't allowed", ephemeral=True ) return - elif remind_at < datetime.utcnow(): + elif remind_at < datetime.now(tz=timezone.utc): pass r = Reminder( diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index f9361c3..a235c3c 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,5 +1,5 @@ """Cog wrapper for command caching.""" -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from dis_snek import Context, Scale, Snake from dis_snek.client.utils.misc_utils import find @@ -35,7 +35,7 @@ class CacheCog(Scale): async def _expire_interaction(self) -> None: keys = list(self.cache.keys()) for key in keys: - if self.cache[key]["timeout"] <= datetime.utcnow() + timedelta(minutes=1): + if self.cache[key]["timeout"] <= datetime.now(tz=timezone.utc) + timedelta(minutes=1): del self.cache[key] From f8e1d88c5e5d7a21cadd7ecd3fb281321a16e8ae Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:32:16 -0600 Subject: [PATCH 131/365] Fix warnings.count -> len(warnings) --- jarvis/cogs/admin/warning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 0c15964..d70edf1 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -97,7 +97,7 @@ class WarningCog(ModcaseCog): if len(active_warns) == 0: embed = build_embed( title="Warnings", - description=f"{warnings.count()} total | 0 currently active", + description=f"{len(warnings)} total | 0 currently active", fields=[], ) embed.set_author(name=user.username, icon_url=user.display_avatar.url) From 7084c7fd055b2483fdbea68b6872d19c790a66eb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 03:36:35 -0600 Subject: [PATCH 132/365] Update warnings embed display --- jarvis/cogs/admin/warning.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index d70edf1..e8b9dbb 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -107,12 +107,13 @@ class WarningCog(ModcaseCog): fields = [] for warn in active_warns: admin = await ctx.guild.fetch_member(warn.admin) + ts = int(warn.created_at.timestamp()) admin_name = "||`[redacted]`||" if admin: admin_name = f"{admin.username}#{admin.discriminator}" fields.append( EmbedField( - name=warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"), + name=f"", value=f"{warn.reason}\nAdmin: {admin_name}\n\u200b", inline=False, ) @@ -135,8 +136,9 @@ class WarningCog(ModcaseCog): else: fields = [] for warn in warnings: + ts = int(warn.created_at.timestamp()) title = "[A] " if warn.active else "[I] " - title += warn.created_at.strftime("%Y-%m-%d %H:%M:%S UTC") + title += f"" fields.append( EmbedField( name=title, From e91b774ffff8b557fcb79046e5ec5297e19a2cdc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:00:07 -0600 Subject: [PATCH 133/365] Use new slash command syntax --- jarvis/cogs/admin/ban.py | 7 +- jarvis/cogs/admin/lockdown.py | 9 +- jarvis/cogs/admin/roleping.py | 44 +++--- jarvis/cogs/autoreact.py | 13 +- jarvis/cogs/ctc2.py | 16 +- jarvis/cogs/dbrand.py | 134 ++++------------ jarvis/cogs/gl.py | 28 ++-- jarvis/cogs/remindme.py | 10 +- jarvis/cogs/rolegiver.py | 24 +-- jarvis/cogs/settings.py | 290 ++++++++++++++++------------------ jarvis/cogs/starboard.py | 25 ++- jarvis/cogs/twitter.py | 14 +- 12 files changed, 252 insertions(+), 362 deletions(-) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 0f18c7d..242c624 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -9,6 +9,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, SlashCommandChoice, slash_command, slash_option, @@ -290,9 +291,9 @@ class BanCog(ModcaseCog): ).save() await ctx.send("Unable to find user in Discord, but removed entry from database.") - @slash_command( - name="bans", description="User bans", sub_cmd_name="list", sub_cmd_description="List bans" - ) + bans = SlashCommand(name="bans", description="User bans") + + @bans.subcommand(sub_cmd_name="list", sub_cmd_description="List bans") @slash_option( name="btype", description="Ban type", diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index e55b39e..527bfa5 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -99,9 +99,12 @@ class LockdownCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( + lockdown = SlashCommand( name="lockdown", description="Manage server-wide lockdown", + ) + + @lockdown.subcommand( sub_cmd_name="start", sub_cmd_description="Lockdown the server", ) @@ -148,7 +151,7 @@ class LockdownCog(Scale): ).commit() await ctx.send("Server now in lockdown.") - @slash_command(name="lockdown", sub_cmd_name="end", sub_cmd_description="End a lockdown") + @lockdown.subcommand(sub_cmd_name="end", sub_cmd_description="End a lockdown") @check(admin_or_permissions(Permissions.MANAGE_CHANNELS)) async def _lockdown_end( self, diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 47c0291..e1cb6d7 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.role import Role from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -27,9 +27,11 @@ class RolepingCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="roleping", - description="Set up warnings for pinging specific roles", + roleping = SlashCommand( + name="roleping", description="Set up warnings for pinging specific roles" + ) + + @roleping.subcommand( sub_cmd_name="add", sub_cmd_description="Add a role to roleping", ) @@ -50,7 +52,7 @@ class RolepingCog(Scale): ).commit() await ctx.send(f"Role `{role.name}` added to roleping.") - @slash_command(name="roleping", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @roleping.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role") @slash_option( name="role", description="Role to remove", opt_type=OptionTypes.ROLE, required=True ) @@ -64,7 +66,7 @@ class RolepingCog(Scale): await roleping.delete() await ctx.send(f"Role `{role.name}` removed from roleping.") - @slash_command(name="roleping", sub_cmd_name="list", description="Lick all blocklisted roles") + @roleping.subcommand(sub_cmd_name="list", sub_cmd_description="Lick all blocklisted roles") async def _roleping_list(self, ctx: InteractionContext) -> None: rolepings = await Roleping.find(q(guild=ctx.guild.id)).to_list(None) @@ -123,11 +125,11 @@ class RolepingCog(Scale): await paginator.send(ctx) - @slash_command( - name="roleping", - description="Block roles from being pinged", - group_name="bypass", - group_description="Allow specific users/roles to ping rolepings", + bypass = roleping.group( + name="bypass", description="Allow specific users/roles to ping rolepings" + ) + + @bypass.subcommand( sub_cmd_name="user", sub_cmd_description="Add a user as a bypass to a roleping", ) @@ -170,11 +172,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.display_name} user bypass added for `{role.name}`") - @slash_command( - name="roleping", - group_name="bypass", + @bypass.subcommand( sub_cmd_name="role", - description="Add a role as a bypass to roleping", + sub_cmd_description="Add a role as a bypass to roleping", ) @slash_option( name="bypass", description="Role to add", opt_type=OptionTypes.ROLE, required=True @@ -207,11 +207,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.name} role bypass added for `{role.name}`") - @slash_command( - name="roleping", - description="Block roles from being pinged", - group_name="restore", - group_description="Remove a roleping bypass", + restore = roleping.group(name="restore", description="Remove a roleping bypass") + + @restore.subcommand( sub_cmd_name="user", sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @@ -238,11 +236,9 @@ class RolepingCog(Scale): await roleping.commit() await ctx.send(f"{bypass.display_name} user bypass removed for `{role.name}`") - @slash_command( - name="roleping", - group_name="restore", + @restore.subcommand( sub_cmd_name="role", - description="Remove a bypass from a roleping (restoring it)", + sub_cmd_description="Remove a bypass from a roleping (restoring it)", ) @slash_option( name="bypass", description="Role to remove", opt_type=OptionTypes.ROLE, required=True diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 40a5012..9d421f2 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -8,7 +8,7 @@ from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -72,8 +72,9 @@ class AutoReactCog(Scale): return True return False - @slash_command( - name="autoreact", + autoreact = SlashCommand(name="autoreact", description="Channel message autoreacts") + + @autoreact.subcommand( sub_cmd_name="add", sub_cmd_description="Add an autoreact emote to a channel", ) @@ -134,8 +135,7 @@ class AutoReactCog(Scale): message += f" Set autoreact thread creation to {thread} in {channel.mention}" await ctx.send(message) - @slash_command( - name="autoreact", + @autoreact.subcommand( sub_cmd_name="remove", sub_cmd_description="Remove an autoreact emote to a channel", ) @@ -178,8 +178,7 @@ class AutoReactCog(Scale): await self.delete_autoreact(ctx, channel) await ctx.send(f"Removed {emote} from {channel.mention} autoreact.") - @slash_command( - name="autoreact", + @autoreact.subcommand( sub_cmd_name="list", sub_cmd_description="List all autoreacts on a channel", ) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 8d4e71b..31658a9 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -7,7 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User -from dis_snek.models.snek.application_commands import slash_command +from dis_snek.models.snek.application_commands import SlashCommand from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -36,18 +36,16 @@ class CTCCog(Scale): def __del__(self): self._session.close() - @slash_command( - name="ctc2", sub_cmd_name="about", description="CTC2 related commands", scopes=guild_ids - ) + ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopres=guild_ids) + + @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _about(self, ctx: InteractionContext) -> None: await ctx.send("See https://completethecode.com for more information") - @slash_command( - name="ctc2", + @ctc2.subcommand( sub_cmd_name="pw", sub_cmd_description="Guess a password for https://completethecodetwo.cards", - scopes=guild_ids, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _pw(self, ctx: InteractionContext, guess: str) -> None: @@ -89,11 +87,9 @@ class CTCCog(Scale): await ctx.send("Nope.", ephemeral=True) _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() - @slash_command( - name="ctc2", + @ctc2.subcommand( sub_cmd_name="guesses", sub_cmd_description="Show guesses made for https://completethecodetwo.cards", - scopes=guild_ids, ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 761ff12..5cea582 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -7,7 +7,7 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.embed import EmbedField from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import cooldown @@ -17,7 +17,7 @@ from jarvis.config import get_config from jarvis.data.dbrand import shipping_lookup from jarvis.utils import build_embed -guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] +guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862402786116763668] class DbrandCog(Scale): @@ -39,121 +39,53 @@ class DbrandCog(Scale): def __del__(self): self._session.close() - @slash_command( - name="db", - sub_cmd_name="skin", - scopes=guild_ids, - sub_cmd_description="See what skins are available", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _skin(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "/skins") + db = SlashCommand(name="db", description="dbrand commands", scopes=guild_ids) - @slash_command( - name="db", - sub_cmd_name="robotcamo", - scopes=guild_ids, - sub_cmd_description="Get some robot camo. Make Tony Stark proud", - ) + @db.subcommand(sub_cmd_name="info", sub_cmd_description="Get useful links") @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _camo(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "robot-camo") + async def _info(self, ctx: InteractionContext) -> None: + urls = [ + f"[Get Skins]({self.base_url + 'skins'})", + f"[Robot Camo]({self.base_url + 'robot-camo'})", + f"[Get a Grip]({self.base_url + 'grip'})", + f"[Shop All Products]({self.base_url + 'shop'})", + f"[Order Status]({self.base_url + 'order-status'})", + f"[dbrand Status]({self.base_url + 'status'})", + f"[Be (not) extorted]({self.base_url + 'not-extortion'})", + "[Robot Camo Wallpapers](https://db.io/wallpapers)", + ] + embed = build_embed( + title="Useful Links", description="\n\n".join(urls), fields=[], color="#FFBB00" + ) + embed.set_footer( + text="dbrand.com", + icon_url="https://dev.zevaryx.com/db_logo.png", + ) + embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png") + embed.set_author( + name="dbrand", url=self.base_url, icon_url="https://dev.zevaryx.com/db_logo.png" + ) + await ctx.send(embed=embed) - @slash_command( - name="db", - sub_cmd_name="grip", - scopes=guild_ids, - sub_cmd_description="See devices with Grip support", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _grip(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "grip") - - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="contact", - scopes=guild_ids, sub_cmd_description="Contact support", ) @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _contact(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="support", - scopes=guild_ids, sub_cmd_description="Contact support", ) @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _support(self, ctx: InteractionContext) -> None: await ctx.send("Contact dbrand support here: " + self.base_url + "contact") - @slash_command( - name="db", - sub_cmd_name="orderstat", - scopes=guild_ids, - sub_cmd_description="Get your order status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _orderstat(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "order-status") - - @slash_command( - name="db", - sub_cmd_name="orders", - scopes=guild_ids, - sub_cmd_description="Get your order status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _orders(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "order-status") - - @slash_command( - name="db", - sub_cmd_name="status", - scopes=guild_ids, - sub_cmd_description="dbrand status", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _status(self, ctx: InteractionContext) -> None: - await ctx.send(self.base_url + "status") - - @slash_command( - name="db", - sub_cmd_name="buy", - scopes=guild_ids, - sub_cmd_description="Give us your money!", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _buy(self, ctx: InteractionContext) -> None: - await ctx.send("Give us your money! " + self.base_url + "shop") - - @slash_command( - name="db", - sub_cmd_name="extortion", - scopes=guild_ids, - sub_cmd_description="(not) extortion", - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _extort(self, ctx: InteractionContext) -> None: - await ctx.send("Be (not) extorted here: " + self.base_url + "not-extortion") - - @slash_command( - name="db", - sub_cmd_name="wallpapers", - sub_cmd_description="Robot Camo Wallpapers", - scopes=guild_ids, - ) - @cooldown(bucket=Buckets.USER, rate=1, interval=30) - async def _wallpapers(self, ctx: InteractionContext) -> None: - await ctx.send("Get robot camo wallpapers here: https://db.io/wallpapers") - - @slash_command( - name="db", + @db.subcommand( sub_cmd_name="ship", sub_cmd_description="Get shipping information for your country", - scopes=guild_ids, ) @slash_option( name="search", @@ -217,7 +149,7 @@ class DbrandCog(Scale): ) embed = build_embed( title="Shipping to {}".format(data["country"]), - sub_cmd_description=description, + description=description, color="#FFBB00", fields=fields, url=self.base_url + "shipping/" + country, @@ -231,7 +163,7 @@ class DbrandCog(Scale): elif not data["is_valid"]: embed = build_embed( title="Check Shipping Times", - sub_cmd_description=( + description=( "Country not found.\nYou can [view all shipping " "destinations here](https://dbrand.com/shipping)" ), @@ -248,7 +180,7 @@ class DbrandCog(Scale): elif not data["shipping_available"]: embed = build_embed( title="Shipping to {}".format(data["country"]), - sub_cmd_description=( + description=( "No shipping available.\nTime to move to a country" " that has shipping available.\nYou can [find a new country " "to live in here](https://dbrand.com/shipping)" diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 6bb92d0..80c6a31 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -11,6 +11,7 @@ from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, SlashCommandChoice, slash_command, slash_option, @@ -35,12 +36,11 @@ class GitlabCog(Scale): # J.A.R.V.I.S. GitLab ID is 29 self.project = self._gitlab.projects.get(29) - @slash_command( - name="gl", - description="Get GitLab info", + gl = SlashCommand(name="gl", description="Get GitLab info", scopes=guild_ids) + + @gl.subcommand( sub_cmd_name="issue", sub_cmd_description="Get an issue from GitLab", - scopes=guild_ids, ) @slash_option(name="id", description="Issue ID", opt_type=OptionTypes.INTEGER, required=True) async def _issue(self, ctx: InteractionContext, id: int) -> None: @@ -104,11 +104,9 @@ class GitlabCog(Scale): ) await ctx.send(embed=embed) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="milestone", sub_cmd_description="Get a milestone from GitLab", - scopes=guild_ids, ) @slash_option( name="id", description="Milestone ID", opt_type=OptionTypes.INTEGER, required=True @@ -160,11 +158,9 @@ class GitlabCog(Scale): ) await ctx.send(embed=embed) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="mr", sub_cmd_description="Get a merge request from GitLab", - scopes=guild_ids, ) @slash_option( name="id", description="Merge Request ID", opt_type=OptionTypes.INTEGER, required=True @@ -272,11 +268,9 @@ class GitlabCog(Scale): ) return embed - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="issues", sub_cmd_description="Get issues from GitLab", - scopes=guild_ids, ) @slash_option( name="state", @@ -328,11 +322,9 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="mrs", sub_cmd_description="Get merge requests from GitLab", - scopes=guild_ids, ) @slash_option( name="state", @@ -386,11 +378,9 @@ class GitlabCog(Scale): await paginator.send(ctx) - @slash_command( - name="gl", + @gl.subcommand( sub_cmd_name="milestones", sub_cmd_description="Get milestones from GitLab", - scopes=guild_ids, ) async def _milestones(self, ctx: InteractionContext) -> None: await ctx.defer() diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index b03271b..cc7ea52 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -16,6 +16,7 @@ from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.snek.application_commands import ( OptionTypes, + SlashCommand, slash_command, slash_option, ) @@ -205,7 +206,9 @@ class RemindmeCog(Scale): return embed - @slash_command(name="reminders", sub_cmd_name="list", sub_cmd_description="List reminders") + reminders = SlashCommand(name="reminders", description="Manage reminders") + + @reminders.subcommand(sub_cmd_name="list", sub_cmd_description="List reminders") async def _list(self, ctx: InteractionContext) -> None: reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: @@ -216,7 +219,7 @@ class RemindmeCog(Scale): await ctx.send(embed=embed) - @slash_command(name="reminders", sub_cmd_name="delete", sub_cmd_description="Delete a reminder") + @reminders.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a reminder") async def _delete(self, ctx: InteractionContext) -> None: reminders = await Reminder.find(q(user=ctx.author.id, active=True)).to_list(None) if not reminders: @@ -303,8 +306,7 @@ class RemindmeCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="reminders", + @reminders.subcommand( sub_cmd_name="fetch", sub_cmd_description="Fetch a reminder that failed to send", ) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 7269f3d..6ab9abe 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.role import Role from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check, cooldown @@ -28,9 +28,9 @@ class RolegiverCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="rolegiver", - description="Allow users to choose their own roles", + rolegiver = SlashCommand(name="rolegiver", description="Allow users to choose their own roles") + + @rolegiver.subcommand( sub_cmd_name="add", sub_cmd_description="Add a role to rolegiver", ) @@ -82,9 +82,7 @@ class RolegiverCog(Scale): await ctx.send(embed=embed) - @slash_command( - name="rolegiver", sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver" - ) + @rolegiver.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role from rolegiver") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_remove(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) @@ -167,7 +165,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command(name="rolegiver", sub_cmd_name="list", description="List rolegiver roles") + @rolegiver.subcommand(sub_cmd_name="list", description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): @@ -202,7 +200,9 @@ class RolegiverCog(Scale): await ctx.send(embed=embed) - @slash_command(name="role", sub_cmd_name="get", sub_cmd_description="Get a role") + role = SlashCommand(name="role", description="Get/Remove Rolegiver roles") + + @role.subcommand(sub_cmd_name="get", sub_cmd_description="Get a role") @cooldown(bucket=Buckets.USER, rate=1, interval=10) async def _role_get(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) @@ -278,7 +278,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command(name="role", sub_cmd_name="remove", sub_cmd_description="Remove a role") + @role.subcommand(sub_cmd_name="remove", sub_cmd_description="Remove a role") @cooldown(bucket=Buckets.USER, rate=1, interval=10) async def _role_remove(self, ctx: InteractionContext) -> None: user_roles = ctx.author.roles @@ -357,8 +357,8 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="rolegiver", sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" + @rolegiver.subcommand( + sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 29afb7d..88cb3c5 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -4,22 +4,19 @@ from typing import Any from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions +from dis_snek.models.discord.role import Role from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check -from discord import Role, TextChannel -from discord.utils import find -from discord_slash import SlashContext, cog_ext -from discord_slash.utils.manage_commands import create_option from jarvis_core.db import q +from jarvis_core.db.models import Setting -from jarvis.db.models import Setting from jarvis.utils import build_embed -from jarvis.utils.field import Field from jarvis.utils.permissions import admin_or_permissions @@ -47,199 +44,175 @@ class SettingsCog(Scale): return await existing.delete() return False - @slash_command( - name="settings", - description="Control bot settings", + settings = SlashCommand(name="settings", description="Control guild settings") + set_ = settings.group(name="set", description="Set a setting") + unset = settings.group(name="unset", description="Unset a setting") + + @set_.subcommand( sub_cmd_name="modlog", - sub_cmd_description="Set ActivityLog channel", + sub_cmd_description="Set Moglod channel", ) @slash_option( name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _set_modlog(self, ctx: InteractionContext(), channel: GuildText) -> None: + async def _set_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: if not isinstance(channel, GuildText): await ctx.send("Channel must be a GuildText", ephemeral=True) return - self.update_settings("modlog", channel.id, ctx.guild.id) + await self.update_settings("modlog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New modlog channel is {channel.mention}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="activitylog", - description="Set activitylog channel", - choices=[ - create_option( - name="channel", - description="Activitylog channel", - opt_type=7, - required=True, - ) - ], + @set_.subcommand( + sub_cmd_name="activitylog", + sub_cmd_description="Set Activitylog channel", + ) + @slash_option( + name="channel", + description="Activitylog Channel", + opt_type=OptionTypes.CHANNEL, + required=True, ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _set_activitylog(self, ctx: SlashContext, channel: TextChannel) -> None: - if not isinstance(channel, TextChannel): - await ctx.send("Channel must be a TextChannel", ephemeral=True) + async def _set_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a GuildText", ephemeral=True) return - self.update_settings("activitylog", channel.id, ctx.guild.id) + await self.update_settings("activitylog", channel.id, ctx.guild.id) await ctx.send(f"Settings applied. New activitylog channel is {channel.mention}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="massmention", - description="Set massmention amount", - choices=[ - create_option( - name="amount", - description="Amount of mentions (0 to disable)", - opt_type=4, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") + @slash_option( + name="amount", + description="Amount of mentions (0 to disable)", + opt_type=OptionTypes.INTEGER, + required=True, ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_massmention(self, ctx: SlashContext, amount: int) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_massmention(self, ctx: InteractionContext, amount: int) -> None: await ctx.defer() - self.update_settings("massmention", amount, ctx.guild.id) + await self.update_settings("massmention", amount, ctx.guild.id) await ctx.send(f"Settings applied. New massmention limit is {amount}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="verified", - description="Set verified role", - choices=[ - create_option( - name="role", - description="verified role", - opt_type=8, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @slash_option( + name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_verified(self, ctx: SlashContext, role: Role) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_verified(self, ctx: InteractionContext, role: Role) -> None: await ctx.defer() - self.update_settings("verified", role.id, ctx.guild.id) + await self.update_settings("verified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New verified role is `{role.name}`") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="unverified", - description="Set unverified role", - choices=[ - create_option( - name="role", - description="Unverified role", - opt_type=8, - required=True, - ) - ], + @set_.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") + @slash_option( + name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_unverified(self, ctx: SlashContext, role: Role) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_unverified(self, ctx: InteractionContext, role: Role) -> None: await ctx.defer() - self.update_settings("unverified", role.id, ctx.guild.id) + await self.update_settings("unverified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New unverified role is `{role.name}`") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="set", - name="noinvite", - description="Set if invite deletion should happen", - choices=[ - create_option( - name="active", - description="Active?", - opt_type=OptionTypes.BOOLEAN, - required=True, - ) - ], + @set_.subcommand( + sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" ) - @check(admin_or_permissions(manage_guild=True)) - async def _set_invitedel(self, ctx: SlashContext, active: bool) -> None: + @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() - self.update_settings("noinvite", active, ctx.guild.id) + await self.update_settings("noinvite", active, ctx.guild.id) await ctx.send(f"Settings applied. Automatic invite active: {active}") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="modlog", - description="Unset modlog channel", + # Unset + @unset.subcommand( + sub_cmd_name="modlog", + sub_cmd_description="Set Moglod channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _unset_modlog(self, ctx: SlashContext) -> None: - self.delete_settings("modlog", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="activitylog", - description="Unset activitylog channel", + @slash_option( + name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True ) - @check(admin_or_permissions(manage_guild=True)) - async def _unset_activitylog(self, ctx: SlashContext) -> None: - self.delete_settings("activitylog", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="massmention", - description="Unet massmention amount", - ) - @check(admin_or_permissions(manage_guild=True)) - async def _massmention(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: await ctx.defer() - self.delete_settings("massmention", ctx.guild.id) - await ctx.send("Setting removed.") + await self.delete_settings("modlog", channel.id, ctx.guild.id) + await ctx.send(f"Setting `{channel.mention}` unset") - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="verified", - description="Unset verified role", + @unset.subcommand( + sub_cmd_name="activitylog", + sub_cmd_description="Set Activitylog channel", ) - @check(admin_or_permissions(manage_guild=True)) - async def _verified(self, ctx: SlashContext) -> None: - await ctx.defer() - self.delete_settings("verified", ctx.guild.id) - await ctx.send("Setting removed.") - - @cog_ext.cog_subcommand( - base="settings", - subcommand_group="unset", - name="unverified", - description="Unset unverified role", + @slash_option( + name="channel", + description="Activitylog Channel", + opt_type=OptionTypes.CHANNEL, + required=True, ) - @check(admin_or_permissions(manage_guild=True)) - async def _unverified(self, ctx: SlashContext) -> None: + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: await ctx.defer() - self.delete_settings("unverified", ctx.guild.id) - await ctx.send("Setting removed.") + await self.delete_settings("activitylog", channel.id, ctx.guild.id) + await ctx.send(f"Setting `{channel.mention}` unset") - @cog_ext.cog_subcommand(base="settings", name="view", description="View settings") - @check(admin_or_permissions(manage_guild=True)) - async def _view(self, ctx: SlashContext) -> None: - settings = Setting.objects(guild=ctx.guild.id) + @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") + @slash_option( + name="amount", + description="Amount of mentions (0 to disable)", + opt_type=OptionTypes.INTEGER, + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_massmention(self, ctx: InteractionContext, amount: int) -> None: + await ctx.defer() + await self.delete_settings("massmention") + await ctx.send(f"Setting `{amount}` unset") + + @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @slash_option( + name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_verified(self, ctx: InteractionContext, role: Role) -> None: + await ctx.defer() + await self.delete_settings("verified") + await ctx.send(f"Setting `{role.name} unset`") + + @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") + @slash_option( + name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_unverified(self, ctx: InteractionContext, role: Role) -> None: + await ctx.defer() + await self.delete_settings("unverified") + await ctx.send(f"Setting `{role.name}` unset") + + @unset.subcommand( + sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" + ) + @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: + await ctx.defer() + await self.delete_settings("noinvite") + await ctx.send(f"Setting `{active}` unset") + + @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _view(self, ctx: InteractionContext) -> None: + settings = Setting.find(q(guild=ctx.guild.id)) fields = [] - for setting in settings: + async for setting in settings: value = setting.value if setting.setting in ["unverified", "verified", "mute"]: - value = find(lambda x: x.id == value, ctx.guild.roles) + value = await ctx.guild.fetch_role(value) if value: value = value.mention else: value = "||`[redacted]`||" elif setting.setting in ["activitylog", "modlog"]: - value = find(lambda x: x.id == value, ctx.guild.text_channels) + value = await ctx.guild.fetch_channel(value) if value: value = value.mention else: @@ -247,22 +220,23 @@ class SettingsCog(Scale): elif setting.setting == "rolegiver": value = "" for _role in setting.value: - nvalue = find(lambda x: x.id == value, ctx.guild.roles) + nvalue = await ctx.guild.fetch_role(value) if value: value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(Field(name=setting.setting, value=value or "N/A")) + fields.append(EmbedField(name=setting.setting, value=value or "N/A", inlilne=False)) embed = build_embed(title="Current Settings", description="", fields=fields) await ctx.send(embed=embed) - @cog_ext.cog_subcommand(base="settings", name="clear", description="Clear all settings") - @check(admin_or_permissions(manage_guild=True)) - async def _clear(self, ctx: SlashContext) -> None: - deleted = Setting.objects(guild=ctx.guild.id).delete() - await ctx.send(f"Guild settings cleared: `{deleted is not None}`") + @settings.subcommand(sub_cmd_name="clear", sub_cmd_description="Clear all settings") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _clear(self, ctx: InteractionContext) -> None: + async for setting in Setting.find(q(guild=ctx.guild.id)): + await setting.delete() + await ctx.send("Guild settings cleared") def setup(bot: Snake) -> None: diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index b74afb4..b0bfccf 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -9,8 +9,8 @@ from dis_snek.models.discord.message import Message from dis_snek.models.snek.application_commands import ( CommandTypes, OptionTypes, + SlashCommand, context_menu, - slash_command, slash_option, ) from dis_snek.models.snek.command import check @@ -36,9 +36,9 @@ class StarboardCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command( - name="starboard", - description="Extra pins! Manage starboards", + starboard = SlashCommand(name="starboard", description="Extra pins! Manage starboards") + + @starboard.subcommand( sub_cmd_name="list", sub_cmd_description="List all starboards", ) @@ -53,9 +53,7 @@ class StarboardCog(Scale): else: await ctx.send("No Starboards available.") - @slash_command( - name="starboard", sub_cmd_name="create", sub_cmd_description="Create a starboard" - ) + @starboard.subcommand(sub_cmd_name="create", sub_cmd_description="Create a starboard") @slash_option( name="channel", description="Starboard channel", @@ -91,9 +89,7 @@ class StarboardCog(Scale): ).commit() await ctx.send(f"Starboard created. Check it out at {channel.mention}.") - @slash_command( - name="starboard", sub_cmd_name="delete", sub_cmd_description="Delete a starboard" - ) + @starboard.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a starboard") @slash_option( name="channel", description="Starboard channel", @@ -229,9 +225,12 @@ class StarboardCog(Scale): async def _star_message(self, ctx: InteractionContext) -> None: await self._star_add(ctx, message=str(ctx.target_id)) - @slash_command( + star = SlashCommand( name="star", description="Manage stars", + ) + + @star.subcommand( sub_cmd_name="add", sub_cmd_description="Star a message", ) @@ -250,9 +249,7 @@ class StarboardCog(Scale): ) -> None: await self._star_add(ctx, message, channel) - @slash_command( - name="star", sub_cmd_name="delete", sub_cmd_description="Delete a starred message" - ) + @star.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete a starred message") @slash_option( name="id", description="Star ID to delete", opt_type=OptionTypes.INTEGER, required=True ) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index aec012f..d3e7af4 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -9,7 +9,7 @@ from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.snek.application_commands import ( OptionTypes, - slash_command, + SlashCommand, slash_option, ) from dis_snek.models.snek.command import check @@ -34,9 +34,12 @@ class TwitterCog(Scale): self._guild_cache = {} self._channel_cache = {} - @slash_command( + twitter = SlashCommand( name="twitter", description="Manage Twitter follows", + ) + + @twitter.subcommand( sub_cmd_name="follow", sub_cmd_description="Follow a Twitter acount", ) @@ -108,9 +111,7 @@ class TwitterCog(Scale): await ctx.send(f"Now following `@{handle}` in {channel.mention}") - @slash_command( - name="twitter", sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts" - ) + @twitter.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Twitter accounts") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _twitter_unfollow(self, ctx: InteractionContext) -> None: t = TwitterFollow.find(q(guild=ctx.guild.id)) @@ -164,8 +165,7 @@ class TwitterCog(Scale): component.disabled = True await message.edit(components=components) - @slash_command( - name="twitter", + @twitter.subcommand( sub_cmd_name="retweets", sub_cmd_description="Modify followed Twitter accounts", ) From 72d91e6bbd8eed5751b3fa13c6c75376a61151be Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:02:10 -0600 Subject: [PATCH 134/365] Fix syntax errors --- jarvis/cogs/rolegiver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 6ab9abe..878ece4 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -165,7 +165,7 @@ class RolegiverCog(Scale): component.disabled = True await message.edit(components=components) - @rolegiver.subcommand(sub_cmd_name="list", description="List rolegiver roles") + @rolegiver.subcommand(sub_cmd_name="list", sub_cmd_description="List rolegiver roles") async def _rolegiver_list(self, ctx: InteractionContext) -> None: setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if not setting or (setting and not setting.roles): @@ -358,7 +358,7 @@ class RolegiverCog(Scale): await message.edit(components=components) @rolegiver.subcommand( - sub_cmd_name="cleanup", description="Removed deleted roles from rolegiver" + sub_cmd_name="cleanup", sub_cmd_description="Removed deleted roles from rolegiver" ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_cleanup(self, ctx: InteractionContext) -> None: From c2c94a71d68f6f426932bbaf9efe8c771d6ef515 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:18:17 -0600 Subject: [PATCH 135/365] More debug logs --- jarvis/client.py | 6 ++++++ jarvis/cogs/admin/__init__.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 04d44bb..01e2cf5 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -53,6 +53,7 @@ class Jarvis(Snake): super().__init__(*args, **kwargs) self.logger = logging.getLogger(__name__) self.phishing_domains = [] + self.pre_run_callback = self._prerun @Task.create(IntervalTrigger(days=1)) async def _update_domains(self) -> None: @@ -72,6 +73,11 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) + async def _prerun(self, ctx: Context, *args, **kwargs) -> None: + name = ctx.invoked_name + args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) + self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") + async def _sync_domains(self) -> None: self.logger.debug("Loading phishing domains") async with ClientSession(headers={"X-Identity": "Discord: zevaryx#5779"}) as session: diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index daaca31..2022554 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,4 +1,6 @@ """J.A.R.V.I.S. Admin Cogs.""" +import logging + from dis_snek import Snake from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning @@ -6,11 +8,21 @@ from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, def setup(bot: Snake) -> None: """Add admin cogs to J.A.R.V.I.S.""" + logger = logging.getLogger(__name__) + msg = "Loaded jarvis.cogs.admin.{}" ban.BanCog(bot) + logger.debug(msg.format("ban")) kick.KickCog(bot) + logger.debug(msg.format("kick")) lock.LockCog(bot) + logger.debug(msg.format("lock")) lockdown.LockdownCog(bot) + logger.debug(msg.format("ban")) mute.MuteCog(bot) + logger.debug(msg.format("mute")) purge.PurgeCog(bot) + logger.debug(msg.format("purge")) roleping.RolepingCog(bot) + logger.debug(msg.format("roleping")) warning.WarningCog(bot) + logger.debug(msg.format("warning")) From 002bf5b1506d46b85858d032ca740c93300b23aa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:19:31 -0600 Subject: [PATCH 136/365] Fix missing option in uuid2ulid and ulid2uuid --- jarvis/cogs/dev.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f746d23..f08bf22 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -168,6 +168,9 @@ class DevCog(Scale): name="uuid2ulid", description="Convert a UUID to a ULID", ) + @slash_option( + name="uuid", description="UUID to convert", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _uuid2ulid(self, ctx: InteractionContext, uuid: str) -> None: if UUID_VERIFY.match(uuid): @@ -180,6 +183,9 @@ class DevCog(Scale): name="ulid2uuid", description="Convert a ULID to a UUID", ) + @slash_option( + name="ulid", description="ULID to convert", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _ulid2uuid(self, ctx: InteractionContext, ulid: str) -> None: if ULID_VERIFY.match(ulid): From 029743f9770e0d631e47068c232254b6ab7ef5a7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:32:24 -0600 Subject: [PATCH 137/365] Lots more event debugging --- jarvis/client.py | 216 ++++++++++++++++++++++++++--------------------- 1 file changed, 121 insertions(+), 95 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 01e2cf5..b0b48eb 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -102,6 +102,7 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """Lepton on_command_error override.""" + self.logger.debug(f"Handling error in {ctx.invoked_name}: {error}") if isinstance(error, CommandOnCooldown): await ctx.send(str(error), ephemeral=True) return @@ -191,7 +192,8 @@ class Jarvis(Snake): guild = user.guild unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: - role = guild.get_role(unverified.value) + self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}") + role = await guild.fetch_role(unverified.value) if role not in user.roles: await user.add_role(role, reason="User just joined and is unverified") @@ -199,6 +201,9 @@ class Jarvis(Snake): """Handle autopurge events.""" autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) if autopurge: + self.logger.debug( + f"Autopurging message {message.guild.id}/{message.channel.id}/{message.id}" + ) await message.delete(delay=autopurge.delay) async def autoreact(self, message: Message) -> None: @@ -210,6 +215,9 @@ class Jarvis(Snake): ) ) if autoreact: + self.logger.debug( + f"Autoreacting to message {message.guild.id}/{message.channel.id}/{message.id}" + ) for reaction in autoreact.reactions: await message.add_reaction(reaction) if autoreact.thread: @@ -240,17 +248,17 @@ class Jarvis(Snake): "VtgZntXcnZ", "gPfYGbvTCE", ] - if match.group(1) not in allowed and setting.value: + if (m := match.group(1)) not in allowed and setting.value: + self.logger.debug(f"Removing non-allowed invite {m} from {message.guild.id}") await message.delete() - w = Warning( + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Sent an invite link", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Sent an invite link") await message.channel.send(embed=embed) @@ -270,15 +278,17 @@ class Jarvis(Snake): - (1 if message.author.id in message._mention_ids else 0) # noqa: W503 > massmention.value # noqa: W503 ): - w = Warning( + self.logger.debug( + f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Mass Mention", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Mass Mention") await message.channel.send(embed=embed) @@ -322,31 +332,35 @@ class Jarvis(Snake): break if role_in_rolepings and user_missing_role and not user_is_admin and not user_has_bypass: - w = Warning( + self.logger.debug( + f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Pinged a blocked role/user with a blocked role", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Pinged a blocked role/user with a blocked role") await message.channel.send(embed=embed) async def phishing(self, message: Message) -> None: """Check if the message contains any known phishing domains.""" for match in url.finditer(message.content): - if match.group("domain") in self.phishing_domains: - w = Warning( + if (m := match.group("domain")) in self.phishing_domains: + self.logger.debug( + f"Phishing url {m} detected in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Phishing URL", user=message.author.id, - ) - await w.commit() + ).commit() embed = warning_embed(message.author, "Phishing URL") await message.channel.send(embed=embed) await message.delete() @@ -365,15 +379,17 @@ class Jarvis(Snake): data = await resp.json() for item in data["processed"]["urls"].values(): if not item["safe"]: - w = Warning( + self.logger.debug( + f"Phishing url {match.string} detected in {message.guild.id}/{message.channel.id}/{message.id}" + ) + await Warning( active=True, admin=self.user.id, duration=24, guild=message.guild.id, reason="Unsafe URL", user=message.author.id, - ) - await w.commit() + ).commit() reasons = ", ".join(item["not_safe_reasons"]) embed = warning_embed(message.author, reasons) await message.channel.send(embed=embed) @@ -404,36 +420,41 @@ class Jarvis(Snake): if modlog: if not before or before.content == after.content or before.content is None: return - channel = before.guild.get_channel(modlog.value) - fields = [ - EmbedField( - "Original Message", - before.content if before.content else "N/A", - False, - ), - EmbedField( - "New Message", - after.content if after.content else "N/A", - False, - ), - ] - embed = build_embed( - title="Message Edited", - description=f"{after.author.mention} edited a message", - fields=fields, - color="#fc9e3f", - timestamp=after.edited_timestamp, - url=after.jump_url, - ) - embed.set_author( - name=after.author.username, - icon_url=after.author.display_avatar.url, - url=after.jump_url, - ) - embed.set_footer( - text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" - ) - await channel.send(embed=embed) + try: + channel = before.guild.get_channel(modlog.value) + fields = [ + EmbedField( + "Original Message", + before.content if before.content else "N/A", + False, + ), + EmbedField( + "New Message", + after.content if after.content else "N/A", + False, + ), + ] + embed = build_embed( + title="Message Edited", + description=f"{after.author.mention} edited a message", + fields=fields, + color="#fc9e3f", + timestamp=after.edited_timestamp, + url=after.jump_url, + ) + embed.set_author( + name=after.author.username, + icon_url=after.author.display_avatar.url, + url=after.jump_url, + ) + embed.set_footer( + text=f"{after.author.username}#{after.author.discriminator} | {after.author.id}" + ) + await channel.send(embed=embed) + except Exception as e: + self.logger.warn( + f"Failed to process edit {before.guild.id}/{before.channel.id}/{before.id}: {e}" + ) if not isinstance(after.channel, DMChannel) and not after.author.bot: await self.massmention(after) await self.roleping(after) @@ -455,53 +476,58 @@ class Jarvis(Snake): content = "N/A" fields = [EmbedField("Original Message", content, False)] - if message.attachments: - value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) - fields.append( - EmbedField( - name="Attachments", - value=value, - inline=False, + try: + if message.attachments: + value = "\n".join([f"[{x.filename}]({x.url})" for x in message.attachments]) + fields.append( + EmbedField( + name="Attachments", + value=value, + inline=False, + ) + ) + + if message.sticker_items: + value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) + fields.append( + EmbedField( + name="Stickers", + value=value, + inline=False, + ) + ) + + if message.embeds: + value = str(len(message.embeds)) + " embeds" + fields.append( + EmbedField( + name="Embeds", + value=value, + inline=False, + ) + ) + + channel = message.guild.get_channel(modlog.value) + embed = build_embed( + title="Message Deleted", + description=f"{message.author.mention}'s message was deleted", + fields=fields, + color="#fc9e3f", + ) + + embed.set_author( + name=message.author.username, + icon_url=message.author.display_avatar.url, + url=message.jump_url, + ) + embed.set_footer( + text=( + f"{message.author.username}#{message.author.discriminator} | " + f"{message.author.id}" ) ) - - if message.sticker_items: - value = "\n".join([f"Sticker: {x.name}" for x in message.sticker_items]) - fields.append( - EmbedField( - name="Stickers", - value=value, - inline=False, - ) + await channel.send(embed=embed) + except Exception as e: + self.logger.warn( + f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}" ) - - if message.embeds: - value = str(len(message.embeds)) + " embeds" - fields.append( - EmbedField( - name="Embeds", - value=value, - inline=False, - ) - ) - - channel = message.guild.get_channel(modlog.value) - embed = build_embed( - title="Message Deleted", - description=f"{message.author.mention}'s message was deleted", - fields=fields, - color="#fc9e3f", - ) - - embed.set_author( - name=message.author.username, - icon_url=message.author.display_avatar.url, - url=message.jump_url, - ) - embed.set_footer( - text=( - f"{message.author.username}#{message.author.discriminator} | " - f"{message.author.id}" - ) - ) - await channel.send(embed=embed) From 3b9a3f721b7c8fd65451b91dd6377e4a01993fc4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 26 Mar 2022 21:40:22 -0600 Subject: [PATCH 138/365] Fix errors in client event processing --- jarvis/client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index b0b48eb..85c9e3e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -241,16 +241,20 @@ class Jarvis(Snake): setting = Setting(guild=message.guild.id, setting="noinvite", value=True) await setting.commit() if match: - guild_invites = await message.guild.invites() - guild_invites.append(message.guild.vanity_url_code) + guild_invites = await message.guild.fetch_invites() + if message.guild.vanity_url_code: + guild_invites.append(message.guild.vanity_url_code) allowed = [x.code for x in guild_invites] + [ "dbrand", "VtgZntXcnZ", "gPfYGbvTCE", ] if (m := match.group(1)) not in allowed and setting.value: - self.logger.debug(f"Removing non-allowed invite {m} from {message.guild.id}") - await message.delete() + self.logger.debug(f"Removing non-allowed invite `{m}` from {message.guild.id}") + try: + await message.delete() + except Exception: + self.logger.debug("Message deleted before action taken") await Warning( active=True, admin=self.user.id, @@ -351,7 +355,7 @@ class Jarvis(Snake): for match in url.finditer(message.content): if (m := match.group("domain")) in self.phishing_domains: self.logger.debug( - f"Phishing url {m} detected in {message.guild.id}/{message.channel.id}/{message.id}" + f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) await Warning( active=True, @@ -380,7 +384,7 @@ class Jarvis(Snake): for item in data["processed"]["urls"].values(): if not item["safe"]: self.logger.debug( - f"Phishing url {match.string} detected in {message.guild.id}/{message.channel.id}/{message.id}" + f"Scam url `{match.string}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) await Warning( active=True, From 46693f2443fb9fe7c1fbe52b5274e887424d98a7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 00:47:38 -0600 Subject: [PATCH 139/365] Make dateparser timezone aware --- jarvis/cogs/remindme.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index cc7ea52..0a7a0c3 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -102,7 +102,7 @@ class RemindmeCog(Scale): base_settings = { "PREFER_DATES_FROM": "future", "TIMEZONE": "UTC", - "RETURN_AS_TIMEZONE_AWARE": False, + "RETURN_AS_TIMEZONE_AWARE": True, } rt_settings = base_settings.copy() rt_settings["PARSERS"] = [ @@ -115,12 +115,11 @@ class RemindmeCog(Scale): at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] at_remind_at = parse(delay, settings=at_settings) - if rt_remind_at and at_remind_at: - remind_at = max(rt_remind_at, at_remind_at) + if rt_remind_at: + remind_at = rt_remind_at + elif at_remind_at: + remind_at = at_remind_at else: - remind_at = rt_remind_at or at_remind_at - - if not remind_at: self.logger.debug(f"Failed to parse delay: {delay}") await response.send( f"`{delay}` is not a parsable date, please try again", ephemeral=True From 3100ea1f62be8ba54832f4a9ef1b8a539e6c6a1d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 02:24:32 -0600 Subject: [PATCH 140/365] Add `/timestamp` --- jarvis/cogs/util.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 61b3da2..d2dd557 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -4,9 +4,11 @@ import platform import re import secrets import string +from datetime import timezone from io import BytesIO import numpy as np +from dateparser import parse from dis_snek import InteractionContext, Scale, Snake, const from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice from dis_snek.models.discord.embed import EmbedField @@ -23,6 +25,7 @@ from dis_snek.models.snek.application_commands import ( from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from PIL import Image +from tzlocal import get_localzone import jarvis from jarvis.data import pigpen @@ -296,6 +299,7 @@ class UtilCog(Scale): if length > 256: await ctx.send("Please limit password to 256 characters", ephemeral=True) return + choices = [ string.ascii_letters, string.hexdigits, @@ -329,6 +333,35 @@ class UtilCog(Scale): outp += "`" await ctx.send(outp[:2000]) + @slash_command( + name="timestamp", description="Convert a datetime or timestamp into it's counterpart" + ) + @slash_option( + name="string", description="String to convert", opt_type=OptionTypes.STRING, required=True + ) + async def _timestamp(self, ctx: InteractionContext, string: str) -> None: + timestamp = parse(string) + if not timestamp: + await ctx.send("Valid time not found, try again", ephemeral=True) + return + + if not timestamp.tzinfo: + timestamp = timestamp.replace(tzinfo=get_localzone()).astimezone(tz=timezone.utc) + + timestamp_utc = timestamp.astimezone(tz=timezone.utc) + + ts = int(timestamp.timestamp()) + ts_utc = int(timestamp_utc.timestamp()) + fields = [ + EmbedField(name="Unix Epoch", value=f"`{ts}`"), + EmbedField(name="Unix Epoch (UTC)", value=f"`{ts_utc}`"), + EmbedField(name="Absolute Time", value=f"\n``"), + EmbedField(name="Relative Time", value=f"\n``"), + EmbedField(name="ISO8601", value=timestamp.isoformat()), + ] + embed = build_embed(title="Converted Time", description="", fields=fields) + await ctx.send(embed=embed) + def setup(bot: Snake) -> None: """Add UtilCog to J.A.R.V.I.S.""" From 9ae61247a0c88e31c913537667d9e3d30865c593 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 27 Mar 2022 02:41:03 -0600 Subject: [PATCH 141/365] Add description to timestamp embed --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index d2dd557..3964a00 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -359,7 +359,7 @@ class UtilCog(Scale): EmbedField(name="Relative Time", value=f"\n``"), EmbedField(name="ISO8601", value=timestamp.isoformat()), ] - embed = build_embed(title="Converted Time", description="", fields=fields) + embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields) await ctx.send(embed=embed) From 2a42ae956c3042088ab759b39cf8b876bc17d98a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 28 Mar 2022 18:15:29 -0600 Subject: [PATCH 142/365] Add `private` flag to /timestamp --- jarvis/cogs/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 3964a00..4cda0c9 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -339,7 +339,10 @@ class UtilCog(Scale): @slash_option( name="string", description="String to convert", opt_type=OptionTypes.STRING, required=True ) - async def _timestamp(self, ctx: InteractionContext, string: str) -> None: + @slash_option( + name="private", description="Respond quietly?", opt_type=OptionTypes.BOOLEAN, required=False + ) + async def _timestamp(self, ctx: InteractionContext, string: str, private: bool = False) -> None: timestamp = parse(string) if not timestamp: await ctx.send("Valid time not found, try again", ephemeral=True) @@ -360,7 +363,7 @@ class UtilCog(Scale): EmbedField(name="ISO8601", value=timestamp.isoformat()), ] embed = build_embed(title="Converted Time", description=f"`{string}`", fields=fields) - await ctx.send(embed=embed) + await ctx.send(embed=embed, ephemeral=private) def setup(bot: Snake) -> None: From 047cf075f25dadb261716250ebc13620e6159f2b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 28 Mar 2022 18:15:49 -0600 Subject: [PATCH 143/365] Add channel context to a few activitylog actions --- jarvis/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 85c9e3e..591fba8 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -176,7 +176,7 @@ class Jarvis(Snake): ] embed = build_embed( title="Command Invoked", - description=f"{ctx.author.mention} invoked a command", + description=f"{ctx.author.mention} invoked a command in {ctx.channel.mention}", fields=fields, color="#fc9e3f", ) @@ -440,7 +440,7 @@ class Jarvis(Snake): ] embed = build_embed( title="Message Edited", - description=f"{after.author.mention} edited a message", + description=f"{after.author.mention} edited a message in {before.channel.mention}", fields=fields, color="#fc9e3f", timestamp=after.edited_timestamp, @@ -514,7 +514,7 @@ class Jarvis(Snake): channel = message.guild.get_channel(modlog.value) embed = build_embed( title="Message Deleted", - description=f"{message.author.mention}'s message was deleted", + description=f"{message.author.mention}'s message was deleted from {message.channel.mention}", fields=fields, color="#fc9e3f", ) From ff71e937206a436e9f3ed4576cf74d086078e947 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 15:30:09 -0600 Subject: [PATCH 144/365] Fix reminders display in /reminders delete --- .pre-commit-config.yaml | 2 +- jarvis/cogs/remindme.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 045ddd0..a2e9a36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: python-check-blanket-noqa - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black args: [--line-length=100, --target-version=py310] diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 0a7a0c3..23b09cf 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -228,7 +228,7 @@ class RemindmeCog(Scale): options = [] for reminder in reminders: option = SelectOption( - label=f"", + label=f"{reminder.remind_at}", value=str(reminder.id), emoji="⏰", ) From f0502d65db34d85b03b62d376d1eac081f7a0d6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 22:11:33 -0600 Subject: [PATCH 145/365] Mostly fix CTC2 --- jarvis/cogs/ctc2.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 31658a9..a007628 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -7,7 +7,11 @@ from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User -from dis_snek.models.snek.application_commands import SlashCommand +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) from dis_snek.models.snek.command import cooldown from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q @@ -15,7 +19,7 @@ from jarvis_core.db.models import Guess from jarvis.utils import build_embed -guild_ids = [578757004059738142, 520021794380447745, 862402786116763668] +guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862402786116763668] valid = re.compile(r"[\w\s\-\\/.!@#$%^*()+=<>,\u0080-\U000E0FFF]*") invites = re.compile( @@ -36,7 +40,7 @@ class CTCCog(Scale): def __del__(self): self._session.close() - ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopres=guild_ids) + ctc2 = SlashCommand(name="ctc2", description="CTC2 related commands", scopes=guild_ids) @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) @@ -47,6 +51,9 @@ class CTCCog(Scale): sub_cmd_name="pw", sub_cmd_description="Guess a password for https://completethecodetwo.cards", ) + @slash_option( + name="guess", description="Guess a password", opt_type=OptionTypes.STRING, required=True + ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _pw(self, ctx: InteractionContext, guess: str) -> None: if len(guess) > 800: @@ -85,7 +92,7 @@ class CTCCog(Scale): correct = True else: await ctx.send("Nope.", ephemeral=True) - _ = Guess(guess=guess, user=ctx.author.id, correct=correct).save() + await Guess(guess=guess, user=ctx.author.id, correct=correct).commit() @ctc2.subcommand( sub_cmd_name="guesses", @@ -93,9 +100,10 @@ class CTCCog(Scale): ) @cooldown(bucket=Buckets.USER, rate=1, interval=2) async def _guesses(self, ctx: InteractionContext) -> None: - guesses = Guess.objects().order_by("-correct", "-id") + await ctx.defer() + guesses = Guess.find().sort("correct", -1).sort("id", -1) fields = [] - for guess in guesses: + async for guess in guesses: user = await ctx.guild.get_member(guess["user"]) if not user: user = await self.bot.fetch_user(guess["user"]) From 2b11fea0cbcddff58ee0ce0527ee988d69c57b62 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 29 Mar 2022 22:12:05 -0600 Subject: [PATCH 146/365] Work on context menu muting --- jarvis/__init__.py | 4 +- jarvis/cogs/admin/mute.py | 122 +++++++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 425519e..34f5af8 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -16,7 +16,6 @@ except Exception: __version__ = "0.0.0" jconfig = JarvisConfig.from_yaml() - logger = get_logger("jarvis") logger.setLevel(jconfig.log_level) file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") @@ -28,8 +27,7 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None - -jarvis = Jarvis(intents=intents, default_prefix="!", sync_interactions=jconfig.sync) +jarvis = Jarvis(intents=intents, sync_interactions=jconfig.sync) async def run() -> None: diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 4ccf7f1..7cc022b 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,13 +1,19 @@ """J.A.R.V.I.S. MuteCog.""" +import asyncio import logging from datetime import datetime, timedelta, timezone +from dateparser import parse +from dateparser_data.settings import default_parsers from dis_snek import InteractionContext, Permissions, Snake from dis_snek.models.discord.embed import EmbedField +from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( + CommandTypes, OptionTypes, SlashCommandChoice, + context_menu, slash_command, slash_option, ) @@ -26,6 +32,96 @@ class MuteCog(ModcaseCog): super().__init__(bot) self.logger = logging.getLogger(__name__) + async def _apply_timeout( + self, ctx: InteractionContext, user: Member, reason: str, until: datetime + ) -> None: + await user.timeout(communication_disabled_until=until, reason=reason) + duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60) + await Mute( + user=user.id, + reason=reason, + admin=ctx.author.id, + guild=ctx.guild.id, + duration=duration, + active=True, + ).commit() + ts = int(until.timestamp()) + + embed = build_embed( + title="User Muted", + description=f"{user.mention} has been muted", + fields=[ + EmbedField(name="Reason", value=reason), + EmbedField(name="Until", value=f" "), + ], + ) + embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) + embed.set_thumbnail(url=user.display_avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + return embed + + @context_menu(name="Mute User", context_type=CommandTypes.USER) + @check( + admin_or_permissions( + Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS + ) + ) + async def _timeout_cm(self, ctx: InteractionContext) -> None: + modal = Modal( + title=f"Muting {ctx.target.mention}", + components=[ + InputText( + label="Reason?", + placeholder="Spamming, harrassment, etc", + style=TextStyles.SHORT, + custom_id="reason", + max_length=100, + ), + InputText( + label="Duration", + placeholder="1h 30m | in 5 minutes | in 4 weeks", + style=TextStyles.SHORT, + custom_id="until", + max_length=100, + ), + ], + ) + await ctx.send_modal(modal) + try: + response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) + reason = response.responses.get("reason") + until = response.responses.get("until") + except asyncio.TimeoutError: + return + base_settings = { + "PREFER_DATES_FROM": "future", + "TIMEZONE": "UTC", + "RETURN_AS_TIMEZONE_AWARE": True, + } + rt_settings = base_settings.copy() + rt_settings["PARSERS"] = [ + x for x in default_parsers if x not in ["absolute-time", "timestamp"] + ] + + rt_until = parse(until, settings=rt_settings) + + at_settings = base_settings.copy() + at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] + at_until = parse(until, settings=at_settings) + + if rt_until: + until = rt_until + elif at_until: + until = at_until + else: + self.logger.debug(f"Failed to parse delay: {until}") + await response.send( + f"`{until}` is not a parsable date, please try again", ephemeral=True + ) + return + embed = await self._apply_timeout(ctx, ctx.target, reason, until) + await response.send(embed=embed) + @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( @@ -77,29 +173,7 @@ class MuteCog(ModcaseCog): return until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) - await user.timeout(communication_disabled_until=until, reason=reason) - m = Mute( - user=user.id, - reason=reason, - admin=ctx.author.id, - guild=ctx.guild.id, - duration=duration, - active=True, - ) - await m.commit() - ts = int(until.timestamp()) - - embed = build_embed( - title="User Muted", - description=f"{user.mention} has been muted", - fields=[ - EmbedField(name="Reason", value=reason), - EmbedField(name="Until", value=f" "), - ], - ) - embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) - embed.set_thumbnail(url=user.display_avatar.url) - embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + embed = await self._apply_timeout(ctx, user, reason, until) await ctx.send(embed=embed) @slash_command(name="unmute", description="Unmute a user") @@ -120,6 +194,8 @@ class MuteCog(ModcaseCog): await ctx.send("User is not muted", ephemeral=True) return + await user.timeout(communication_disabled_until=0) + embed = build_embed( title="User Unmuted", description=f"{user.mention} has been unmuted", From 278f8b07905579514ee802ce94646981305987fe Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 09:50:52 -0600 Subject: [PATCH 147/365] Fix paste error --- jarvis/client.py | 6 +++--- poetry.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 591fba8..bae626e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -133,10 +133,10 @@ class Jarvis(Snake): ) error_message = "".join(traceback.format_exception(error)) if len(full_message + error_message) >= 1800: - error_message = " ".join(error_message.split("\n")) + error_message = "\n ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message - paste = Paste(content=full_message) - await paste.save(DEFAULT_SITE) + paste = Paste(content=full_message, site=DEFAULT_SITE) + await paste.save() self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") await channel.send( diff --git a/poetry.lock b/poetry.lock index c50d2db..a3067a7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -622,7 +622,7 @@ test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] [[package]] name = "python-lsp-server" -version = "1.4.0" +version = "1.4.1" description = "Python Language Server for the Language Server Protocol" category = "dev" optional = false @@ -1608,8 +1608,8 @@ python-lsp-jsonrpc = [ {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, ] python-lsp-server = [ - {file = "python-lsp-server-1.4.0.tar.gz", hash = "sha256:769142c07573f6b66e930cbd7c588b826082550bef6267bb0aec63e7b6260009"}, - {file = "python_lsp_server-1.4.0-py3-none-any.whl", hash = "sha256:3160c97c6d1edd8456f262fc0e4aad6b322c0cfd1b58d322a41679e57787594d"}, + {file = "python-lsp-server-1.4.1.tar.gz", hash = "sha256:be7f83298af9f0951a93972cafc9db04fd7cf5c05f20812515275f0ba70e342f"}, + {file = "python_lsp_server-1.4.1-py3-none-any.whl", hash = "sha256:e6bd0cf9530d8e2ba3b9c0ae10b99a16c33808bc9a7266afecc3900017b35326"}, ] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, From 5e51a9a20331c93887e9f514091d56014935d523 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:12:27 -0600 Subject: [PATCH 148/365] Fix unmute, past dates on mute --- jarvis/cogs/admin/mute.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 7cc022b..3ad3a56 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -109,6 +109,7 @@ class MuteCog(ModcaseCog): at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] at_until = parse(until, settings=at_settings) + old_until = until if rt_until: until = rt_until elif at_until: @@ -119,6 +120,11 @@ class MuteCog(ModcaseCog): f"`{until}` is not a parsable date, please try again", ephemeral=True ) return + if until < datetime.now(tz=timezone.utc): + await response.send( + f"`{old_until}` is in the past, which isn't allowed", ephemeral=True + ) + return embed = await self._apply_timeout(ctx, ctx.target, reason, until) await response.send(embed=embed) @@ -194,7 +200,7 @@ class MuteCog(ModcaseCog): await ctx.send("User is not muted", ephemeral=True) return - await user.timeout(communication_disabled_until=0) + await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc)) embed = build_embed( title="User Unmuted", From 87e3b18a77cd303f20bdf298e55c5305d52f1f11 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:13:18 -0600 Subject: [PATCH 149/365] Better command logging --- jarvis/client.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index bae626e..410dc17 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -73,8 +73,10 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) - async def _prerun(self, ctx: Context, *args, **kwargs) -> None: + async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: name = ctx.invoked_name + if ctx.target: + kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") @@ -115,6 +117,8 @@ class Jarvis(Snake): timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" + if ctx.target: + ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " if isinstance(v, str) and len(v) > 100: @@ -136,8 +140,8 @@ class Jarvis(Snake): error_message = "\n ".join(error_message.split("\n")) full_message += "Exception: |\n " + error_message paste = Paste(content=full_message, site=DEFAULT_SITE) - await paste.save() - self.logger.debug(f"Large traceback, saved to Pasty {paste.id}") + key = await paste.save() + self.logger.debug(f"Large traceback, saved to Pasty {paste.id}, {key=}") await channel.send( f"JARVIS encountered an error at {timestamp}. Log too big to send over Discord." @@ -160,6 +164,8 @@ class Jarvis(Snake): if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] + if ctx.target: + args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): v = v.replace("`", "\\`") From 989d8daff874f4c1daf1f85409e92821e2df2060 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:20:16 -0600 Subject: [PATCH 150/365] Fix target_id check in logs --- jarvis/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 410dc17..bb90256 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -75,7 +75,7 @@ class Jarvis(Snake): async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: name = ctx.invoked_name - if ctx.target: + if ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") @@ -117,7 +117,7 @@ class Jarvis(Snake): timestamp = int(datetime.now().timestamp()) timestamp = f"" arg_str = "" - if ctx.target: + if ctx.target_id: ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " @@ -164,7 +164,7 @@ class Jarvis(Snake): if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] - if ctx.target: + if ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): From f28646c6e6de99af6889336c59be592b8079588f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:22:13 -0600 Subject: [PATCH 151/365] Fix invite link bypass in encode --- jarvis/cogs/dev.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index f08bf22..c7b5e6d 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -211,6 +211,12 @@ class DevCog(Scale): required=True, ) async def _encode(self, ctx: InteractionContext, method: str, data: str) -> None: + if invites.search(data): + await ctx.send( + "Please don't use this to bypass invite restrictions", + ephemeral=True, + ) + return mstr = method method = getattr(base64, method + "encode") try: From 427f4a9cf99036e7b8f11fb72a24aca9aed8dcda Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:26:52 -0600 Subject: [PATCH 152/365] Fix remaining datetime.now calls --- jarvis/client.py | 2 +- jarvis/utils/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index bb90256..7cfef60 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -114,7 +114,7 @@ class Jarvis(Snake): guild = await self.fetch_guild(DEFAULT_GUILD) channel = await guild.fetch_channel(DEFAULT_ERROR_CHANNEL) error_time = datetime.now(tz=timezone.utc).strftime("%d-%m-%Y %H:%M-%S.%f UTC") - timestamp = int(datetime.now().timestamp()) + timestamp = int(datetime.now(tz=timezone.utc).timestamp()) timestamp = f"" arg_str = "" if ctx.target_id: diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 6b0d89a..3c5de79 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,5 +1,5 @@ """J.A.R.V.I.S. Utility Functions.""" -from datetime import datetime +from datetime import datetime, timezone from pkgutil import iter_modules import git @@ -23,7 +23,7 @@ def build_embed( ) -> Embed: """Embed builder utility function.""" if not timestamp: - timestamp = datetime.now() + timestamp = datetime.now(tz=timezone.utc) embed = Embed( title=title, description=description, From 5bb592183e7639d2529b2d49abc4d2f88c349b01 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 10:59:18 -0600 Subject: [PATCH 153/365] Add `notify` setting, fix settings management --- jarvis/cogs/settings.py | 70 +++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 88cb3c5..4eebd93 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -124,79 +124,75 @@ class SettingsCog(Scale): await self.update_settings("noinvite", active, ctx.guild.id) await ctx.send(f"Settings applied. Automatic invite active: {active}") + @set_.subcommand(sub_cmd_name="notify", sub_cmd_description="Notify users of admin action?") + @slash_option(name="active", description="Notify?", opt_type=OptionTypes.BOOLEAN, required=True) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _set_notify(self, ctx: InteractionContext, active: bool) -> None: + await ctx.defer() + await self.update_settings("notify", active, ctx.guild.id) + await ctx.send(f"Settings applied. Notifications active: {active}") + # Unset @unset.subcommand( sub_cmd_name="modlog", - sub_cmd_description="Set Moglod channel", - ) - @slash_option( - name="channel", description="ModLog Channel", opt_type=OptionTypes.CHANNEL, required=True + sub_cmd_description="Unset Modlog channel", ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_modlog(self, ctx: InteractionContext, channel: GuildText) -> None: + async def _unset_modlog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("modlog", channel.id, ctx.guild.id) - await ctx.send(f"Setting `{channel.mention}` unset") + await self.delete_settings("modlog") + await ctx.send("Setting `modlog` unset") @unset.subcommand( sub_cmd_name="activitylog", - sub_cmd_description="Set Activitylog channel", - ) - @slash_option( - name="channel", - description="Activitylog Channel", - opt_type=OptionTypes.CHANNEL, - required=True, + sub_cmd_description="Unset Activitylog channel", ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_activitylog(self, ctx: InteractionContext, channel: GuildText) -> None: + async def _unset_activitylog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("activitylog", channel.id, ctx.guild.id) - await ctx.send(f"Setting `{channel.mention}` unset") + await self.delete_settings("activitylog") + await ctx.send("Setting `activitylog` unset") - @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Set massmention output") - @slash_option( - name="amount", - description="Amount of mentions (0 to disable)", - opt_type=OptionTypes.INTEGER, - required=True, - ) + @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Unset massmention output") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_massmention(self, ctx: InteractionContext, amount: int) -> None: + async def _unset_massmention(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("massmention") - await ctx.send(f"Setting `{amount}` unset") + await ctx.send("Setting `massmention` unset") - @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Set verified role") + @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Unset verified role") @slash_option( name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_verified(self, ctx: InteractionContext, role: Role) -> None: + async def _unset_verified(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("verified") - await ctx.send(f"Setting `{role.name} unset`") + await ctx.send("Setting `massmention` unset") - @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Set unverified role") - @slash_option( - name="role", description="Unverified role", opt_type=OptionTypes.ROLE, required=True - ) + @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Unset unverified role") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _unset_unverified(self, ctx: InteractionContext, role: Role) -> None: + async def _unset_unverified(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("unverified") - await ctx.send(f"Setting `{role.name}` unset") + await ctx.send("Setting `unverified` unset") @unset.subcommand( - sub_cmd_name="noinvite", sub_cmd_description="Set if invite deletion should happen" + sub_cmd_name="noinvite", sub_cmd_description="Unset if invite deletion should happen" ) - @slash_option(name="active", description="Active?", opt_type=OptionTypes.BOOLEAN, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() await self.delete_settings("noinvite") await ctx.send(f"Setting `{active}` unset") + @unset.subcommand(sub_cmd_name="notify", sub_cmd_description="Unset admin action notifications") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _unset_notify(self, ctx: InteractionContext) -> None: + await ctx.defer() + await self.delete_settings("noinvite") + await ctx.send("Setting `notify` unset") + @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _view(self, ctx: InteractionContext) -> None: From ee86320c1604e08786cf4e17c3b5536dd225e957 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 11:35:08 -0600 Subject: [PATCH 154/365] Fix ModcaseCog logging --- jarvis/utils/cogs.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index a235c3c..0a30954 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,12 +1,24 @@ """Cog wrapper for command caching.""" from datetime import datetime, timedelta, timezone -from dis_snek import Context, Scale, Snake +from dis_snek import InteractionContext, Scale, Snake from dis_snek.client.utils.misc_utils import find +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q -from jarvis_core.db.models import Action, Ban, Kick, Modlog, Mute, Note, Warning +from jarvis_core.db.models import ( + Action, + Ban, + Kick, + Modlog, + Mute, + Note, + Setting, + Warning, +) + +from jarvis.utils import build_embed MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} @@ -19,7 +31,7 @@ class CacheCog(Scale): self.cache = {} self._expire_interaction.start() - def check_cache(self, ctx: Context, **kwargs: dict) -> dict: + def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: """Check the cache.""" if not kwargs: kwargs = {} @@ -46,7 +58,7 @@ class ModcaseCog(Scale): self.bot = bot self.add_scale_postrun(self.log) - async def log(self, ctx: Context, *args: list, **kwargs: dict) -> None: + async def log(self, ctx: InteractionContext, *args: list, **kwargs: dict) -> None: """ Log a moderation activity in a moderation case. @@ -57,19 +69,35 @@ class ModcaseCog(Scale): if name not in ["Lock", "Lockdown", "Purge", "Roleping"]: user = kwargs.pop("user", None) - if not user: - # Log warning about missing user + if not user and not ctx.target_id: + self.logger.warn(f"Admin action {name} missing user, exiting") return + elif ctx.target_id: + user = ctx.target coll = MODLOG_LOOKUP.get(name, None) if not coll: - # Log warning about unsupported action + self.logger.warn(f"Unsupported action {name}, exiting") return action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) if not action: - # Log warning about missing action + self.logger.warn(f"Missing action {name}, exiting") return action = Action(action_type=name.lower(), parent=action.id) note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() + notify = await Setting.find_one(q(guild=ctx.guild.id, setting="notify", value=True)) + if notify and name not in ["Kick", "Ban"]: # Ignore Kick and Ban, as these are unique + fields = [ + EmbedField(name="Action Type", value=name, inline=False), + EmbedField( + name="Reason", value=kwargs.get("reason", None) or "N/A", inline=False + ), + ] + embed = build_embed( + title="Admin action taken", + description=f"Admin action has been taken against you in {ctx.guild.name}", + fields=fields, + ) + await user.send(embed=embed) From 27b286dab51fab8710b954f2ae5cf4d975b7a9ea Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:25:09 -0600 Subject: [PATCH 155/365] Fix saving settings --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 4eebd93..79c2b47 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -33,7 +33,7 @@ class SettingsCog(Scale): if not existing: existing = Setting(setting=setting, guild=guild, value=value) existing.value = value - updated = existing.save() + updated = await existing.commit() return updated is not None From 58c093bfa198bca9ca1d2183efe2d134c8a43580 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:25:24 -0600 Subject: [PATCH 156/365] Delete unused commands --- jarvis/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 34f5af8..2849f32 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -27,7 +27,9 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES restart_ctx = None -jarvis = Jarvis(intents=intents, sync_interactions=jconfig.sync) +jarvis = Jarvis( + intents=intents, sync_interactions=jconfig.sync, delete_unused_application_cmds=True +) async def run() -> None: From 0de6999ba6787701fc61403794c40c564485f5b8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:36:48 -0600 Subject: [PATCH 157/365] Update notify DM to look better --- jarvis/utils/cogs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 0a30954..7994290 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -100,4 +100,7 @@ class ModcaseCog(Scale): description=f"Admin action has been taken against you in {ctx.guild.name}", fields=fields, ) + guild_url = f"https://discord.com/channels/{ctx.guild.id}" + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url) + embed.set_thumbnail(url=ctx.guild.icon.url) await user.send(embed=embed) From 502c5c2ad199ebffc6f3421753df31124f7bd1bc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 30 Mar 2022 19:40:42 -0600 Subject: [PATCH 158/365] Add extra info to util command --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 4cda0c9..efa0c03 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -50,7 +50,7 @@ class UtilCog(Scale): @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _status(self, ctx: InteractionContext) -> None: title = "J.A.R.V.I.S. Status" - desc = "All systems online" + desc = f"All systems online\nConnected to **{len(self.bot.guilds)}** guilds" color = "#3498db" fields = [] From de063c14114c204599dfa737835e278501d3a047 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 00:15:59 -0600 Subject: [PATCH 159/365] Add context menu for userinfo --- jarvis/cogs/util.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index efa0c03..0c284a2 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -17,8 +17,10 @@ from dis_snek.models.discord.guild import Guild from dis_snek.models.discord.role import Role from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( + CommandTypes, OptionTypes, SlashCommandChoice, + context_menu, slash_command, slash_option, ) @@ -178,16 +180,6 @@ class UtilCog(Scale): await ctx.send(embed=embed, file=color_show) - @slash_command( - name="userinfo", - description="Get user info", - ) - @slash_option( - name="user", - description="User to get info of", - opt_type=OptionTypes.USER, - required=False, - ) async def _userinfo(self, ctx: InteractionContext, user: User = None) -> None: await ctx.defer() if not user: @@ -233,6 +225,23 @@ class UtilCog(Scale): await ctx.send(embed=embed) + @slash_command( + name="userinfo", + description="Get user info", + ) + @slash_option( + name="user", + description="User to get info of", + opt_type=OptionTypes.USER, + required=False, + ) + async def _userinfo_slsh(self, ctx: InteractionContext, user: User = None) -> None: + await self._userinfo() + + @context_menu(name="User Info", context_type=CommandTypes.USER) + async def _userinfo_menu(self, ctx: InteractionContext) -> None: + await self._userinfo(ctx, ctx.target) + @slash_command(name="serverinfo", description="Get server info") async def _server_info(self, ctx: InteractionContext) -> None: guild: Guild = ctx.guild From b5b7c8ca81ba342e56d5033245323ad50d6df387 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 00:17:52 -0600 Subject: [PATCH 160/365] Fix missing args in userinfo --- jarvis/cogs/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 0c284a2..dd07f19 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -236,7 +236,7 @@ class UtilCog(Scale): required=False, ) async def _userinfo_slsh(self, ctx: InteractionContext, user: User = None) -> None: - await self._userinfo() + await self._userinfo(ctx, user) @context_menu(name="User Info", context_type=CommandTypes.USER) async def _userinfo_menu(self, ctx: InteractionContext) -> None: From be0307fedc88ce7e2c139ef7b40fa1b76c627e85 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 31 Mar 2022 09:22:47 -0600 Subject: [PATCH 161/365] Remove unnecessary dev dependencies --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 110df1f..3a00631 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,6 @@ aiohttp = "^3.8.1" pastypy = "^1.0.1" dateparser = "^1.1.1" -[tool.poetry.dev-dependencies] -python-lsp-server = {extras = ["all"], version = "^1.3.3"} -black = "^22.1.0" - [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 20a4c741931ee4f8a386313796feb5bddec37152 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 4 Apr 2022 19:47:42 -0600 Subject: [PATCH 162/365] Fix on_member_join --- jarvis/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/client.py b/jarvis/client.py index 7cfef60..817fc82 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -193,6 +193,7 @@ class Jarvis(Snake): await channel.send(embed=embed) # Events + @listen() async def on_member_join(self, user: Member) -> None: """Handle on_member_join event.""" guild = user.guild From b950bf12552b15d1c8217bd4158ae0c45b4c3098 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 12 Apr 2022 10:56:45 -0600 Subject: [PATCH 163/365] Fix bug in rolegiver preventing saving of roles --- jarvis/cogs/rolegiver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 878ece4..5b7c436 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -50,7 +50,7 @@ class RolegiverCog(Scale): return setting.roles.append(role.id) - setting.save() + await setting.commit() roles = [] for role_id in setting.roles: @@ -118,7 +118,7 @@ class RolegiverCog(Scale): if role: removed_roles.append(role) setting.roles.remove(int(to_delete)) - setting.save() + await setting.commit() for row in components: for component in row.components: @@ -369,7 +369,7 @@ class RolegiverCog(Scale): for role_id in setting.roles: if role_id not in guild_role_ids: setting.roles.remove(role_id) - setting.save() + await setting.commit() await ctx.send("Rolegiver cleanup finished") From 13ebc33ae1cb0980cdef00d6186dec8dacc57ec7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 12 Apr 2022 11:10:48 -0600 Subject: [PATCH 164/365] Add guild name to error handling --- jarvis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 817fc82..be20753 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -32,6 +32,7 @@ DEFAULT_SITE = "https://paste.zevs.me" ERROR_MSG = """ Command Information: + Guild: {guild_name} Name: {invoked_name} Args: {arg_str} @@ -129,6 +130,7 @@ class Jarvis(Snake): "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" ) full_message = ERROR_MSG.format( + guild_name=ctx.guild.name, error_time=error_time, invoked_name=ctx.invoked_name, arg_str=arg_str, From 48338fc22b6cff79637361071bc026b3971a61d8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 13 Apr 2022 13:05:21 -0600 Subject: [PATCH 165/365] Fix unsetting settings --- jarvis/cogs/settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 79c2b47..722ed6c 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -140,7 +140,7 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_modlog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("modlog") + await self.delete_settings("modlog", ctx.guild.id) await ctx.send("Setting `modlog` unset") @unset.subcommand( @@ -150,14 +150,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_activitylog(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("activitylog") + await self.delete_settings("activitylog", ctx.guild.id) await ctx.send("Setting `activitylog` unset") @unset.subcommand(sub_cmd_name="massmention", sub_cmd_description="Unset massmention output") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_massmention(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("massmention") + await self.delete_settings("massmention", ctx.guild.id) await ctx.send("Setting `massmention` unset") @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Unset verified role") @@ -167,14 +167,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_verified(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("verified") + await self.delete_settings("verified", ctx.guild.id) await ctx.send("Setting `massmention` unset") @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Unset unverified role") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_unverified(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("unverified") + await self.delete_settings("unverified", ctx.guild.id) await ctx.send("Setting `unverified` unset") @unset.subcommand( @@ -183,14 +183,14 @@ class SettingsCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_invitedel(self, ctx: InteractionContext, active: bool) -> None: await ctx.defer() - await self.delete_settings("noinvite") + await self.delete_settings("noinvite", ctx.guild.id) await ctx.send(f"Setting `{active}` unset") @unset.subcommand(sub_cmd_name="notify", sub_cmd_description="Unset admin action notifications") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_notify(self, ctx: InteractionContext) -> None: await ctx.defer() - await self.delete_settings("noinvite") + await self.delete_settings("notify", ctx.guild.id) await ctx.send("Setting `notify` unset") @settings.subcommand(sub_cmd_name="view", sub_cmd_description="View settings") From 835acb8be5c6b8ecdfb539963f7b66adf0ed2c17 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Thu, 14 Apr 2022 23:06:38 -0600 Subject: [PATCH 166/365] Fix issue with rolegiver and get vs fetch --- jarvis/cogs/rolegiver.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 5b7c436..07cdcfe 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -56,7 +56,7 @@ class RolegiverCog(Scale): for role_id in setting.roles: if role_id == role.id: continue - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -92,7 +92,7 @@ class RolegiverCog(Scale): options = [] for role in setting.roles: - role: Role = await ctx.guild.get_role(role) + role: Role = await ctx.guild.fetch_role(role) option = SelectOption(label=role.name, value=str(role.id)) options.append(option) @@ -114,7 +114,7 @@ class RolegiverCog(Scale): ) removed_roles = [] for to_delete in context.context.values: - role = await ctx.guild.get_role(to_delete) + role = await ctx.guild.fetch_role(to_delete) if role: removed_roles.append(role) setting.roles.remove(int(to_delete)) @@ -126,7 +126,7 @@ class RolegiverCog(Scale): roles = [] for role_id in setting.roles: - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -174,7 +174,7 @@ class RolegiverCog(Scale): roles = [] for role_id in setting.roles: - e_role = await ctx.guild.get_role(role_id) + e_role = await ctx.guild.fetch_role(role_id) if not e_role: continue roles.append(e_role) @@ -212,7 +212,7 @@ class RolegiverCog(Scale): options = [] for role in setting.roles: - role: Role = await ctx.guild.get_role(role) + role: Role = await ctx.guild.fetch_role(role) option = SelectOption(label=role.name, value=str(role.id)) options.append(option) @@ -235,7 +235,7 @@ class RolegiverCog(Scale): added_roles = [] for role in context.context.values: - role = await ctx.guild.get_role(int(role)) + role = await ctx.guild.fetch_role(int(role)) added_roles.append(role) await ctx.author.add_role(role, reason="Rolegiver") From 2d91771f590c98a070dcb4fdd1b1fead0897bc5e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:10:51 -0600 Subject: [PATCH 167/365] Silently drop reminder deletion errors, closes #131 --- jarvis/cogs/remindme.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 23b09cf..b4f3ff8 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -276,7 +276,10 @@ class RemindmeCog(Scale): inline=False, ) ) - await reminder.delete() + try: + await reminder.delete() + except Exception: + pass # Silently drop error for row in components: for component in row.components: From f1061786733025cf40ad7ffd43f8d66679a080fb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:15:08 -0600 Subject: [PATCH 168/365] Make reminder fetch error ephemeral, closes #132 --- jarvis/cogs/remindme.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index b4f3ff8..fb6044f 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -318,7 +318,7 @@ class RemindmeCog(Scale): async def _fetch(self, ctx: InteractionContext, id: str) -> None: reminder = await Reminder.find_one(q(id=id)) if not reminder: - await ctx.send(f"Reminder {id} does not exist") + await ctx.send(f"Reminder `{id}` does not exist", hidden=True) return embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) @@ -329,6 +329,11 @@ class RemindmeCog(Scale): embed.set_thumbnail(url=ctx.author.display_avatar) await ctx.send(embed=embed, ephemeral=reminder.private) + if reminder.remind_at <= datetime.now(tz=timezone.utc): + try: + await reminder.delete() + except Exception: + pass # Silently drop error def setup(bot: Snake) -> None: From 90dfb77d1172f1446c77a220aa7867a6d59f4c1d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:38:04 -0600 Subject: [PATCH 169/365] Disable send command tracebacks --- jarvis/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 2849f32..fc18c43 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -28,7 +28,10 @@ intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.G restart_ctx = None jarvis = Jarvis( - intents=intents, sync_interactions=jconfig.sync, delete_unused_application_cmds=True + intents=intents, + sync_interactions=jconfig.sync, + delete_unused_application_cmds=True, + send_command_tracebacks=False, ) From 395e7f74fb77d390f94e9de27b3e38b13effc33a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 18 Apr 2022 16:50:43 -0600 Subject: [PATCH 170/365] Fix settings typo --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 722ed6c..da89c9a 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -221,7 +221,7 @@ class SettingsCog(Scale): value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(EmbedField(name=setting.setting, value=value or "N/A", inlilne=False)) + fields.append(EmbedField(name=setting.setting, value=value or "N/A", inline=False)) embed = build_embed(title="Current Settings", description="", fields=fields) From e0924b085f6c1298db798bf1f7c028dd3bea9224 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:12:10 -0600 Subject: [PATCH 171/365] Fix settings view --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index da89c9a..9ef4b5c 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -221,7 +221,7 @@ class SettingsCog(Scale): value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" - fields.append(EmbedField(name=setting.setting, value=value or "N/A", inline=False)) + fields.append(EmbedField(name=setting.setting, value=str(value) or "N/A", inline=False)) embed = build_embed(title="Current Settings", description="", fields=fields) From 3f9c344cc7fb40fb4c201dfeb5ea279061fa805c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:28:36 -0600 Subject: [PATCH 172/365] Fix error in fetching rolegiver info --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 9ef4b5c..02d8ed0 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -216,7 +216,7 @@ class SettingsCog(Scale): elif setting.setting == "rolegiver": value = "" for _role in setting.value: - nvalue = await ctx.guild.fetch_role(value) + nvalue = await ctx.guild.fetch_role(_role) if value: value += "\n" + nvalue.mention else: From d79fc0e99402daf11e45ebf5340f01d86612089b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:31:52 -0600 Subject: [PATCH 173/365] Fix not getting existing role in settings view --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 02d8ed0..0ac0af5 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -217,7 +217,7 @@ class SettingsCog(Scale): value = "" for _role in setting.value: nvalue = await ctx.guild.fetch_role(_role) - if value: + if nvalue: value += "\n" + nvalue.mention else: value += "\n||`[redacted]`||" From a220b29303f4d964c85f3d1f0d3bf98734a282f1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:34:35 -0600 Subject: [PATCH 174/365] Update member events, fix error if activitylog channel no longer exists --- jarvis/client.py | 50 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index be20753..2458802 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -6,14 +6,19 @@ from datetime import datetime, timezone from aiohttp import ClientSession from dis_snek import Snake, listen -from dis_snek.api.events.discord import MessageCreate, MessageDelete, MessageUpdate +from dis_snek.api.events.discord import ( + MemberAdd, + MemberRemove, + MessageCreate, + MessageDelete, + MessageUpdate, +) from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message -from dis_snek.models.discord.user import Member from dis_snek.models.snek.context import Context, InteractionContext from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger @@ -162,7 +167,7 @@ class Jarvis(Snake): async def on_command(self, ctx: InteractionContext) -> None: """Lepton on_command override.""" if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: - modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] @@ -192,13 +197,21 @@ class Jarvis(Snake): embed.set_footer( text=f"{ctx.author.user.username}#{ctx.author.discriminator} | {ctx.author.id}" ) - await channel.send(embed=embed) + if channel: + await channel.send(embed=embed) + else: + self.logger.warning( + f"Activitylog channel no longer exists in {ctx.guild.name}, removing" + ) + await modlog.delete() # Events + # Member @listen() - async def on_member_join(self, user: Member) -> None: - """Handle on_member_join event.""" - guild = user.guild + async def on_member_add(self, event: MemberAdd) -> None: + """Handle on_member_add event.""" + user = event.member + guild = event.guild unverified = await Setting.find_one(q(guild=guild.id, setting="unverified")) if unverified: self.logger.debug(f"Applying unverified role to {user.id} in {guild.id}") @@ -206,6 +219,25 @@ class Jarvis(Snake): if role not in user.roles: await user.add_role(role, reason="User just joined and is unverified") + @listen() + async def on_member_remove(self, event: MemberRemove) -> None: + """Handle on_member_remove event.""" + user = event.member + guild = event.guild + log = await Setting.find_one(q(guild=guild.id, setting="activitylog")) + if log: + self.logger.debug(f"User {user.id} left {guild.id}") + channel = await guild.fetch_channel(log.channel) + embed = build_embed( + title="Member Left", + desciption=f"{user.username}#{user.discriminator} left {guild.name}", + fields=[], + ) + embed.set_author(name=user.username, icon_url=user.avatar.url) + embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") + await channel.send(embed=embed) + + # Message async def autopurge(self, message: Message) -> None: """Handle autopurge events.""" autopurge = await Autopurge.find_one(q(guild=message.guild.id, channel=message.channel.id)) @@ -429,7 +461,7 @@ class Jarvis(Snake): before = event.before after = event.after if not after.author.bot: - modlog = await Setting.find_one(q(guild=after.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=after.guild.id, setting="activitylog")) if modlog: if not before or before.content == after.content or before.content is None: return @@ -481,7 +513,7 @@ class Jarvis(Snake): async def on_message_delete(self, event: MessageDelete) -> None: """Process on_message_delete events.""" message = event.message - modlog = await Setting.find_one(q(guild=message.guild.id, setting="modlog")) + modlog = await Setting.find_one(q(guild=message.guild.id, setting="activitylog")) if modlog: try: content = message.content or "N/A" From 2235479b4015424a3e9e135eb6bafc18515af8e5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:35:14 -0600 Subject: [PATCH 175/365] Fix issue with reminder not found --- jarvis/cogs/remindme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index fb6044f..54a51fc 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -318,7 +318,7 @@ class RemindmeCog(Scale): async def _fetch(self, ctx: InteractionContext, id: str) -> None: reminder = await Reminder.find_one(q(id=id)) if not reminder: - await ctx.send(f"Reminder `{id}` does not exist", hidden=True) + await ctx.send(f"Reminder `{id}` does not exist", ephemeral=True) return embed = build_embed(title="You have a reminder!", description=reminder.message, fields=[]) From 785f67c9c4cd777b990e2d2fa02b1e22f8b8b91a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:41:21 -0600 Subject: [PATCH 176/365] No longer allow warning user not in guild --- jarvis/cogs/admin/warning.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index e8b9dbb..031ee86 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -5,7 +5,7 @@ from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import User +from dis_snek.models.discord.user import Member from dis_snek.models.snek.application_commands import ( OptionTypes, slash_command, @@ -44,7 +44,7 @@ class WarningCog(ModcaseCog): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _warn( - self, ctx: InteractionContext, user: User, reason: str, duration: int = 24 + self, ctx: InteractionContext, user: Member, reason: str, duration: int = 24 ) -> None: if len(reason) > 100: await ctx.send("Reason must be < 100 characters", ephemeral=True) @@ -55,6 +55,9 @@ class WarningCog(ModcaseCog): elif duration >= 120: await ctx.send("Duration must be < 5 days", ephemeral=True) return + if not await ctx.guild.fetch_member(user.id): + await ctx.send("User not in guild", ephemeral=True) + return await ctx.defer() await Warning( user=user.id, @@ -76,7 +79,7 @@ class WarningCog(ModcaseCog): required=False, ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) - async def _warnings(self, ctx: InteractionContext, user: User, active: bool = True) -> None: + async def _warnings(self, ctx: InteractionContext, user: Member, active: bool = True) -> None: warnings = ( await Warning.find( q( From a20449f7b24d40e8db185b723a7118ba4125ad71 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:47:38 -0600 Subject: [PATCH 177/365] Fix star add issues with deleted channels --- jarvis/cogs/starboard.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index b0bfccf..27681b1 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -2,7 +2,6 @@ import logging from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.channel import GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption from dis_snek.models.discord.message import Message @@ -130,8 +129,21 @@ class StarboardCog(Scale): return channel_list = [] + to_delete = [] for starboard in starboards: - channel_list.append(find(lambda x: x.id == starboard.channel, ctx.guild.channels)) + channel = await ctx.guild.fetch_channel(starboard.channel) + if channel: + channel_list.append(channel) + else: + to_delete.append(starboard) + + for starboard in to_delete: + await starboard.delete() + + select_channels = [] + for idx, x in enumerate(channel_list): + if x: + select_channels.append(SelectOption(label=x.name, value=str(idx))) select_channels = [ SelectOption(label=x.name, value=str(idx)) for idx, x in enumerate(channel_list) From e66b15b2d60903ec5f9d5c93216d87e5cc1a4852 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:50:12 -0600 Subject: [PATCH 178/365] Fix name conflicts in star add --- jarvis/cogs/starboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 27681b1..66f94a4 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -131,9 +131,9 @@ class StarboardCog(Scale): channel_list = [] to_delete = [] for starboard in starboards: - channel = await ctx.guild.fetch_channel(starboard.channel) - if channel: - channel_list.append(channel) + c = await ctx.guild.fetch_channel(starboard.channel) + if c: + channel_list.append(c) else: to_delete.append(starboard) From 6862c13fe4b561e49278a1d1cb4eb8862137e166 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 11:55:09 -0600 Subject: [PATCH 179/365] Remove invalid starboards --- jarvis/cogs/starboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 66f94a4..35516a0 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -132,9 +132,12 @@ class StarboardCog(Scale): to_delete = [] for starboard in starboards: c = await ctx.guild.fetch_channel(starboard.channel) - if c: + if c and isinstance(c, GuildText): channel_list.append(c) else: + self.logger.warn( + f"Starboard {starboard.channel} no longer valid in {ctx.guild.name}" + ) to_delete.append(starboard) for starboard in to_delete: From 410ee3c462f4a4fdc58ead84c70cbd375a028d1e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 14:06:43 -0600 Subject: [PATCH 180/365] Disallow default role from role fields --- jarvis/cogs/admin/roleping.py | 4 ++++ jarvis/cogs/rolegiver.py | 4 ++++ jarvis/cogs/settings.py | 6 ++++++ jarvis/cogs/util.py | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index e1cb6d7..e1ead45 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -43,6 +43,10 @@ class RolepingCog(Scale): await ctx.send(f"Role `{role.name}` already in roleping.", ephemeral=True) return + if role.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` to roleping", ephemeral=True) + return + _ = await Roleping( role=role.id, guild=ctx.guild.id, diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 07cdcfe..e159828 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -37,6 +37,10 @@ class RolegiverCog(Scale): @slash_option(name="role", description="Role to add", opt_type=OptionTypes.ROLE, required=True) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _rolegiver_add(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` to rolegiver", ephemeral=True) + return + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if setting and role.id in setting.roles: await ctx.send("Role already in rolegiver", ephemeral=True) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 0ac0af5..d609111 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -100,6 +100,9 @@ class SettingsCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_verified(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot set verified to `@everyone`", ephemeral=True) + return await ctx.defer() await self.update_settings("verified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New verified role is `{role.name}`") @@ -110,6 +113,9 @@ class SettingsCog(Scale): ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _set_unverified(self, ctx: InteractionContext, role: Role) -> None: + if role.id == ctx.guild.id: + await ctx.send("Cannot set unverified to `@everyone`", ephemeral=True) + return await ctx.defer() await self.update_settings("unverified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New unverified role is `{role.name}`") diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index dd07f19..0976a54 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -146,7 +146,7 @@ class UtilCog(Scale): async def _roleinfo(self, ctx: InteractionContext, role: Role) -> None: fields = [ EmbedField(name="ID", value=str(role.id), inline=True), - EmbedField(name="Name", value=role.name, inline=True), + EmbedField(name="Name", value=role.mention, inline=True), EmbedField(name="Color", value=str(role.color.hex), inline=True), EmbedField(name="Mention", value=f"`{role.mention}`", inline=True), EmbedField(name="Hoisted", value="Yes" if role.hoist else "No", inline=True), From a0bb1a6c989f1f76d270ee1b3eb842f3532db1fc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 14:51:36 -0600 Subject: [PATCH 181/365] Catch URL errors in resize --- jarvis/cogs/image.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index d15fdd8..bcd57db 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -94,9 +94,20 @@ class ImageCog(Scale): filename = url.split("/")[-1] data = None - async with self._session.get(url) as resp: - if resp.status == 200: - data = await resp.read() + try: + async with self._session.get(url) as resp: + resp.raise_for_status() + if resp.content_type in ["image/jpeg", "image/png"]: + data = await resp.read() + else: + await ctx.send( + "Unsupported content type. Please send a URL to a JPEG or PNG", + ephemeral=True, + ) + return + except Exception: + await ctx.send("Failed to retrieve image. Please verify url", ephemeral=True) + return size = len(data) if size <= tgt_size: From 05527a74a31db6022151a5b64df6627afec0bcaa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 15:33:42 -0600 Subject: [PATCH 182/365] Allow users with MANAGE_GUILD permissions to be exempt from roleping --- jarvis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 2458802..12b0380 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -339,6 +339,8 @@ class Jarvis(Snake): async def roleping(self, message: Message) -> None: """Handle roleping events.""" + if message.author.has_permission(Permissions.MANAGE_GUILD): + return if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: return rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) From f5cba3ce3a8044549182a1f6b8f195501b03b45a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 15:44:23 -0600 Subject: [PATCH 183/365] Add URL button to ctc2 about --- jarvis/cogs/ctc2.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index a007628..ee061e0 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -5,6 +5,7 @@ import re import aiohttp from dis_snek import InteractionContext, Scale, Snake from dis_snek.ext.paginators import Paginator +from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import Member, User from dis_snek.models.snek.application_commands import ( @@ -45,7 +46,14 @@ class CTCCog(Scale): @ctc2.subcommand(sub_cmd_name="about") @cooldown(bucket=Buckets.USER, rate=1, interval=30) async def _about(self, ctx: InteractionContext) -> None: - await ctx.send("See https://completethecode.com for more information") + components = [ + ActionRow( + Button(style=ButtonStyles.URL, url="https://completethecode.com", label="More Info") + ) + ] + await ctx.send( + "See https://completethecode.com for more information", components=components + ) @ctc2.subcommand( sub_cmd_name="pw", From 6c1753d5d2258bf618bd20d343d20e104120f46a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:01:29 -0600 Subject: [PATCH 184/365] Silently ignore db deletion errors, closes #134 --- jarvis/cogs/admin/roleping.py | 8 +++++++- jarvis/cogs/remindme.py | 4 ++-- jarvis/cogs/starboard.py | 5 ++++- jarvis/cogs/twitter.py | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index e1ead45..7f62bf7 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -67,7 +67,10 @@ class RolepingCog(Scale): await ctx.send("Roleping does not exist", ephemeral=True) return - await roleping.delete() + try: + await roleping.delete() + except Exception: + self.logger.debug("Ignoring deletion error") await ctx.send(f"Role `{role.name}` removed from roleping.") @roleping.subcommand(sub_cmd_name="list", sub_cmd_description="Lick all blocklisted roles") @@ -190,6 +193,9 @@ class RolepingCog(Scale): async def _roleping_bypass_role( self, ctx: InteractionContext, bypass: Role, role: Role ) -> None: + if bypass.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` as a bypass", ephemeral=True) + return roleping = await Roleping.find_one(q(guild=ctx.guild.id, role=role.id)) if not roleping: await ctx.send(f"Roleping not configured for {role.mention}", ephemeral=True) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 54a51fc..9623d5a 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -279,7 +279,7 @@ class RemindmeCog(Scale): try: await reminder.delete() except Exception: - pass # Silently drop error + self.logger.debug("Ignoring deletion error") for row in components: for component in row.components: @@ -333,7 +333,7 @@ class RemindmeCog(Scale): try: await reminder.delete() except Exception: - pass # Silently drop error + self.logger.debug("Ignoring deletion error") def setup(bot: Snake) -> None: diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 35516a0..e285b4e 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -141,7 +141,10 @@ class StarboardCog(Scale): to_delete.append(starboard) for starboard in to_delete: - await starboard.delete() + try: + await starboard.delete() + except Exception: + self.logger.debug("Ignoring deletion error") select_channels = [] for idx, x in enumerate(channel_list): diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index d3e7af4..6bc5416 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -150,7 +150,10 @@ class TwitterCog(Scale): ) for to_delete in context.context.values: follow = get(twitters, guild=ctx.guild.id, twitter_id=int(to_delete)) - await follow.delete() + try: + await follow.delete() + except Exception: + self.logger.debug("Ignoring deletion error") for row in components: for component in row.components: component.disabled = True From c4e822d7321ca44e1d5371640fd733c77c27bf35 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:14:01 -0600 Subject: [PATCH 185/365] Add confirmation to settings clear, closes #82 --- jarvis/cogs/settings.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index d609111..e293da8 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,9 +1,11 @@ """J.A.R.V.I.S. Settings Management Cog.""" +import asyncio import logging from typing import Any from dis_snek import InteractionContext, Scale, Snake from dis_snek.models.discord.channel import GuildText +from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.role import Role @@ -236,9 +238,33 @@ class SettingsCog(Scale): @settings.subcommand(sub_cmd_name="clear", sub_cmd_description="Clear all settings") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _clear(self, ctx: InteractionContext) -> None: - async for setting in Setting.find(q(guild=ctx.guild.id)): - await setting.delete() - await ctx.send("Guild settings cleared") + components = [ + ActionRow( + Button(style=ButtonStyles.RED, emoji="✖️", custom_id="no"), + Button(style=ButtonStyles.GREEN, emoji="✔️", custom_id="yes"), + ) + ] + message = await ctx.send("***Are you sure?***", components=components) + try: + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, + timeout=60 * 5, + ) + if context.context.custom_id == "yes": + async for setting in Setting.find(q(guild=ctx.guild.id)): + await setting.delete() + await ctx.send("Guild settings cleared") + else: + for row in components: + for component in row.components: + component.disabled = True + await message.edit(components=components) + except asyncio.TimeoutError: + for row in components: + for component in row.components: + component.disabled = True + await message.edit(components=components) def setup(bot: Snake) -> None: From d278ca8b203c9643a0eb2989b706e5058ed83e42 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:15:55 -0600 Subject: [PATCH 186/365] Fix response to settings clear --- jarvis/cogs/settings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index e293da8..9ef4c41 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -251,15 +251,15 @@ class SettingsCog(Scale): messages=message, timeout=60 * 5, ) + content = "***Are you sure?***" if context.context.custom_id == "yes": async for setting in Setting.find(q(guild=ctx.guild.id)): await setting.delete() - await ctx.send("Guild settings cleared") - else: - for row in components: - for component in row.components: - component.disabled = True - await message.edit(components=components) + content = "Guild settings cleared" + for row in components: + for component in row.components: + component.disabled = True + await context.context.edit_origin(content=content, components=components) except asyncio.TimeoutError: for row in components: for component in row.components: From 6d051dc47a9817775c3cd4f76b1557108b0823b6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:17:29 -0600 Subject: [PATCH 187/365] More clear message on settings clear no confirmation --- jarvis/cogs/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 9ef4c41..c471cc2 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -256,6 +256,8 @@ class SettingsCog(Scale): async for setting in Setting.find(q(guild=ctx.guild.id)): await setting.delete() content = "Guild settings cleared" + else: + content = "Guild settings not cleared" for row in components: for component in row.components: component.disabled = True @@ -264,7 +266,7 @@ class SettingsCog(Scale): for row in components: for component in row.components: component.disabled = True - await message.edit(components=components) + await message.edit(content="Guild settings not cleared", components=components) def setup(bot: Snake) -> None: From b5242aabbe5a1d7cf4b171091c8d06f78e6e4d32 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:54:10 -0600 Subject: [PATCH 188/365] Add basic message commands for getting log info --- jarvis/cogs/botutil.py | 46 +++ poetry.lock | 627 +++-------------------------------------- 2 files changed, 92 insertions(+), 581 deletions(-) create mode 100644 jarvis/cogs/botutil.py diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py new file mode 100644 index 0000000..e897742 --- /dev/null +++ b/jarvis/cogs/botutil.py @@ -0,0 +1,46 @@ +"""JARVIS bot utility commands.""" +import logging +from io import BytesIO + +from aiofile import AIOFile, LineReader +from dis_snek import MessageContext, Scale, Snake +from dis_snek.models.discord.file import File +from dis_snek.models.snek.command import message_command + + +class BotutilCog(Scale): + """JARVIS Bot Utility Cog.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + + @message_command(name="tail") + async def _tail(self, ctx: MessageContext, count: int = 10) -> None: + if ctx.author.id != self.bot.owner.id: + return + lines = [] + async with AIOFile("jarvis.log", "r") as af: + async for line in LineReader(af): + lines.append(line) + if len(lines) == count: + lines.pop(0) + log = "\n".join(lines) + await ctx.reply(content=f"```\n{log}\n```") + + @message_command(name="log") + async def _log(self, ctx: MessageContext) -> None: + if ctx.author.id != self.bot.owner.id: + return + + async with AIOFile("jarvis.log", "r") as af: + with BytesIO() as file_bytes: + async for chunk in af.read(8192): + file_bytes.write(chunk) + log = File(file_bytes, file_name="jarvis.log") + await ctx.reply(content="Here's the latest log", file=log) + + +def setup(bot: Snake) -> None: + """Add BotutilCog to J.A.R.V.I.S.""" + BotutilCog(bot) diff --git a/poetry.lock b/poetry.lock index a3067a7..74b3af0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,17 @@ +[[package]] +name = "aiofile" +version = "3.7.4" +description = "Asynchronous file operations." +category = "main" +optional = false +python-versions = ">3.4.*, <4" + +[package.dependencies] +caio = ">=0.9.0,<0.10.0" + +[package.extras] +develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] + [[package]] name = "aiohttp" version = "3.8.1" @@ -29,18 +43,6 @@ python-versions = ">=3.6" [package.dependencies] frozenlist = ">=1.1.0" -[[package]] -name = "astroid" -version = "2.9.3" -description = "An abstract syntax tree for Python with inference support." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -wrapt = ">=1.11,<1.14" - [[package]] name = "async-timeout" version = "4.0.2" @@ -64,37 +66,15 @@ tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)" tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] -name = "autopep8" -version = "1.6.0" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -category = "dev" +name = "caio" +version = "0.9.5" +description = "Asynchronous file IO for Linux Posix and Windows." +category = "main" optional = false -python-versions = "*" - -[package.dependencies] -pycodestyle = ">=2.8.0" -toml = "*" - -[[package]] -name = "black" -version = "22.1.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = ">=1.1.0" +python-versions = ">=3.5.*, <4" [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +develop = ["aiomisc", "pytest", "pytest-cov"] [[package]] name = "certifi" @@ -115,25 +95,6 @@ python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] -[[package]] -name = "click" -version = "8.0.4" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "dateparser" version = "1.1.1" @@ -178,19 +139,6 @@ python-versions = ">=3.7" [package.dependencies] typing_extensions = ">=4,<5" -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" - [[package]] name = "frozenlist" version = "1.3.0" @@ -229,20 +177,6 @@ category = "main" optional = false python-versions = ">=3.5" -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] -plugins = ["setuptools"] - [[package]] name = "jarvis-core" version = "0.7.0" @@ -256,6 +190,7 @@ develop = false dis-snek = "*" motor = "^2.5.1" orjson = "^3.6.6" +pytz = "^2022.1" PyYAML = "^6.0" umongo = "^3.1.0" @@ -263,30 +198,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "b81ea66d12b1a32c8291cbe9e14fe17b8e020d08" - -[[package]] -name = "jedi" -version = "0.18.1" -description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -parso = ">=0.8.0,<0.9.0" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.7.1" -description = "A fast and thorough lazy object proxy." -category = "dev" -optional = false -python-versions = ">=3.6" +resolved_reference = "15521e73fb84fb4282a484ff0c9bb88ed1d144ae" [[package]] name = "marshmallow" @@ -305,14 +217,6 @@ docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", " lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "mongoengine" version = "0.23.1" @@ -346,14 +250,6 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "numpy" version = "1.22.3" @@ -410,18 +306,6 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - [[package]] name = "pastypy" version = "1.0.1" @@ -434,25 +318,17 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} urllib3 = {version = "1.26.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version < \"4\""} yarl = {version = "1.7.2", markers = "python_version >= \"3.6\""} -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - [[package]] name = "pillow" version = "9.0.1" @@ -461,30 +337,6 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "platformdirs" -version = "2.5.1" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - [[package]] name = "psutil" version = "5.9.0" @@ -496,14 +348,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "pycryptodome" version = "3.14.1" @@ -512,44 +356,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "pydocstyle" -version = "6.1.1" -description = "Python docstring style checker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -snowballstemmer = "*" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pylint" -version = "2.12.2" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -astroid = ">=2.9.0,<2.10" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" -platformdirs = ">=2.2.0" -toml = ">=0.9.2" - [[package]] name = "pymongo" version = "3.12.3" @@ -606,56 +412,6 @@ requests-toolbelt = ">=0.9.1" autocompletion = ["argcomplete (>=1.10.0,<3)"] yaml = ["PyYaml (>=5.2)"] -[[package]] -name = "python-lsp-jsonrpc" -version = "1.0.0" -description = "JSON RPC 2.0 server library" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -ujson = ">=3.0.0" - -[package.extras] -test = ["pylint", "pycodestyle", "pyflakes", "pytest", "pytest-cov", "coverage"] - -[[package]] -name = "python-lsp-server" -version = "1.4.1" -description = "Python Language Server for the Language Server Protocol" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -autopep8 = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"all\""} -flake8 = {version = ">=4.0.0,<4.1.0", optional = true, markers = "extra == \"all\""} -jedi = ">=0.17.2,<0.19.0" -mccabe = {version = ">=0.6.0,<0.7.0", optional = true, markers = "extra == \"all\""} -pluggy = ">=1.0.0" -pycodestyle = {version = ">=2.8.0,<2.9.0", optional = true, markers = "extra == \"all\""} -pydocstyle = {version = ">=2.0.0", optional = true, markers = "extra == \"all\""} -pyflakes = {version = ">=2.4.0,<2.5.0", optional = true, markers = "extra == \"all\""} -pylint = {version = ">=2.5.0", optional = true, markers = "extra == \"all\""} -python-lsp-jsonrpc = ">=1.0.0" -rope = {version = ">=0.10.5", optional = true, markers = "extra == \"all\""} -ujson = ">=3.0.0" -yapf = {version = "*", optional = true, markers = "extra == \"all\""} - -[package.extras] -all = ["autopep8 (>=1.6.0,<1.7.0)", "flake8 (>=4.0.0,<4.1.0)", "mccabe (>=0.6.0,<0.7.0)", "pycodestyle (>=2.8.0,<2.9.0)", "pydocstyle (>=2.0.0)", "pyflakes (>=2.4.0,<2.5.0)", "pylint (>=2.5.0)", "rope (>=0.10.5)", "yapf"] -autopep8 = ["autopep8 (>=1.6.0,<1.7.0)"] -flake8 = ["flake8 (>=4.0.0,<4.1.0)"] -mccabe = ["mccabe (>=0.6.0,<0.7.0)"] -pycodestyle = ["pycodestyle (>=2.8.0,<2.9.0)"] -pydocstyle = ["pydocstyle (>=2.0.0)"] -pyflakes = ["pyflakes (>=2.4.0,<2.5.0)"] -pylint = ["pylint (>=2.5.0)"] -rope = ["rope (>0.10.5)"] -test = ["pylint (>=2.5.0)", "pytest", "pytest-cov", "coverage", "numpy", "pandas", "matplotlib", "pyqt5", "flaky"] -yapf = ["yapf"] - [[package]] name = "pytz" version = "2022.1" @@ -735,17 +491,6 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" -[[package]] -name = "rope" -version = "0.23.0" -description = "a python refactoring library..." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -dev = ["build", "pytest", "pytest-timeout"] - [[package]] name = "six" version = "1.16.0" @@ -762,22 +507,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" version = "2.0.1" @@ -837,14 +566,6 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""} devenv = ["black", "pyroma", "pytest-cov", "zest.releaser"] test = ["pytest-mock (>=3.3)", "pytest (>=4.3)"] -[[package]] -name = "ujson" -version = "5.1.0" -description = "Ultra fast JSON encoder and decoder for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - [[package]] name = "ulid-py" version = "1.1.0" @@ -883,22 +604,6 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "wrapt" -version = "1.13.3" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "yapf" -version = "0.32.0" -description = "A formatter for Python code." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "yarl" version = "1.7.2" @@ -914,9 +619,13 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ce783f7855bb480b335731403679dc8249644570965a4cff6091eb377c5472df" +content-hash = "ef0ec99d8c98190d25cda0b8f4420738e569fd73b605dbae2a366a392a9cca7d" [metadata.files] +aiofile = [ + {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, + {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, +] aiohttp = [ {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, @@ -995,10 +704,6 @@ aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] -astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, -] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1007,34 +712,22 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] -autopep8 = [ - {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, - {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, -] -black = [ - {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, - {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, - {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, - {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, - {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, - {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, - {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, - {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, - {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, - {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, - {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, - {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, - {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, - {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, - {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, - {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, - {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, - {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, - {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +caio = [ + {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, + {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, + {file = "caio-0.9.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:013aa374158c5074b3c65a0da6b9c6b20a987d85fb317dd077b045e84e2478e1"}, + {file = "caio-0.9.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d767faf537a9ea774e8408ba15a0f1dc734f06857c2d28bdf4258a63b5885f42"}, + {file = "caio-0.9.5-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:97d9a10522a8a25798229fc1113cfaba3832b1cd0c1a3648b009b9740ef5e054"}, + {file = "caio-0.9.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4fe9eff5cf7a2d6f3f418aeeccd11ce9a38329e07527b6f52da085edb44bc2fd"}, + {file = "caio-0.9.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8b6be369139edd678817dc0a313392d710f66fb521c275dce0a9067089b346b"}, + {file = "caio-0.9.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3ffc6259239e03962f9e14829e02795ca9d196eedf32fe61688ba6ed33da46c8"}, + {file = "caio-0.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7a19dfdec6736affb645da233a6007c2590678490d2a1e0f1fb82a696c0a1ddf"}, + {file = "caio-0.9.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15f70d27e1009d279e4f9ff86290aad00b0511ce82a1879c40745244f0a9ec92"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0427a58c1814a091bfbb84318d344fdb9a68f3d49adc74e5fdc7bc9478e1e4fe"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f48fa58e5f699b428f1fd85e394ecec05be4048fcaf1fdf1981b748cd1e03a6"}, + {file = "caio-0.9.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01061288391020f28e1ab8b0437420f7fe1e0ecc29b4107f7a8dcf7789f33b22"}, + {file = "caio-0.9.5-py3-none-any.whl", hash = "sha256:3c74d84dff2bec5f93685cf2f32eb22e4cc5663434a9be5f4a759247229b69b3"}, + {file = "caio-0.9.5.tar.gz", hash = "sha256:167d9342a807bae441b2e88f9ecb62da2f236b319939a8679f68f510a0194c40"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1044,14 +737,6 @@ charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] -click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] dateparser = [ {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, @@ -1064,10 +749,6 @@ discord-typings = [ {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, {file = "discord_typings-0.3.1-py3-none-any.whl", hash = "sha256:65890c467751daa025dcef15683c32160f07427baf83380cfdf11d84ceec980a"}, ] -flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] frozenlist = [ {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, @@ -1141,62 +822,11 @@ idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] jarvis-core = [] -jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, -] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, - {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, -] marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, @@ -1266,10 +896,6 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] numpy = [ {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, @@ -1343,18 +969,10 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -parso = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, -] pastypy = [ {file = "pastypy-1.0.1-py3-none-any.whl", hash = "sha256:63cc664568f86f6ddeb7e5687422bbf4b338d067ea887ed240223c8cbcf6fd2d"}, {file = "pastypy-1.0.1.tar.gz", hash = "sha256:0393d1635b5031170eae3efaf376b14c3a4af7737c778d7ba7d56f2bd25bf5b1"}, ] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] pillow = [ {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, @@ -1392,14 +1010,6 @@ pillow = [ {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] -platformdirs = [ - {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, - {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] psutil = [ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618"}, @@ -1434,10 +1044,6 @@ psutil = [ {file = "psutil-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3"}, {file = "psutil-5.9.0.tar.gz", hash = "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25"}, ] -pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] pycryptodome = [ {file = "pycryptodome-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:75a3a364fee153e77ed889c957f6f94ec6d234b82e7195b117180dcc9fc16f96"}, {file = "pycryptodome-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:aae395f79fa549fb1f6e3dc85cf277f0351e15a22e6547250056c7f0c990d6a5"}, @@ -1470,18 +1076,6 @@ pycryptodome = [ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"}, {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"}, ] -pydocstyle = [ - {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, - {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, -] -pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] -pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, -] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -1603,14 +1197,6 @@ python-gitlab = [ {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, ] -python-lsp-jsonrpc = [ - {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"}, - {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, -] -python-lsp-server = [ - {file = "python-lsp-server-1.4.1.tar.gz", hash = "sha256:be7f83298af9f0951a93972cafc9db04fd7cf5c05f20812515275f0ba70e342f"}, - {file = "python_lsp_server-1.4.1-py3-none-any.whl", hash = "sha256:e6bd0cf9530d8e2ba3b9c0ae10b99a16c33808bc9a7266afecc3900017b35326"}, -] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, @@ -1742,10 +1328,6 @@ requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] -rope = [ - {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, - {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1754,14 +1336,6 @@ smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1782,58 +1356,6 @@ tzlocal = [ {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, ] -ujson = [ - {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"}, - {file = "ujson-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0cae4a9c141856f7ad1a79c17ff1aaebf7fd8faa2f2c2614c37d6f82ed261d96"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba63b789d83ca92237dbc72041a268d91559f981c01763a107105878bae442e"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4e8f71e2fd42dce245bace7e2aa97dabef13926750a351eadca89a1e0f1abd"}, - {file = "ujson-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f73946c047a38640b1f5a2a459237b7bdc417ab028a76c796e4eea984b359b9"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afe91153c2046fa8210b92def513124e0ea5b87ad8fa4c14fef8197204b980f1"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b1ef400fc73ab0cb61b74a662ad4207917223aba6f933a9fea9b0fbe75de2361"}, - {file = "ujson-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c8a884d60dd2eed2fc95a9474d57ead82adf254f54caffb3d9e8ed185c49aba"}, - {file = "ujson-5.1.0-cp310-cp310-win32.whl", hash = "sha256:173b90a2c2836ee42f708df88ecfe3efbc4d868df73c9fcea8cb8f6f3ab93892"}, - {file = "ujson-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c45ad95e82155372d9908774db46e0ef7880af28a734d0b14eaa4f505e64982"}, - {file = "ujson-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4155a7c29bf330329519027c815e15e381c1fff22f50d26f135584d482bbd95d"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa616d0d3c594785c6e9b7f42686bb1c86f9e64aa0f30a72c86d8eb315f54194"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a48efcb5d3695b295c26835ed81048da8cd40e76c4fde2940c807aa452b560c9"}, - {file = "ujson-5.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838d35eb9006d36f9241e95958d9f4819bcf1ea2ec155daf92d5751c31bcc62b"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:05aa6c7297a22081f65497b6f586de6b7060ea47c3ecda80896f47200e9dbf04"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ce441ab7ad1db592e2db95b6c2a1eb882123532897340afac1342c28819e9833"}, - {file = "ujson-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9937e819196b894ffd00801b24f1042dabda142f355313c3f20410993219bc4f"}, - {file = "ujson-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:06bed66ae62d517f67a61cf53c056800b35ef364270723168a1db62702e2d30c"}, - {file = "ujson-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:74e41a0222e6e8136e38f103d6cc228e4e20f1c35cc80224a42804fd67fb35c8"}, - {file = "ujson-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7bbb87f040e618bebe8c6257b3e4e8ae2f708dcbff3270c84718b3360a152799"}, - {file = "ujson-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:68e38122115a8097fbe1cfe52979a797eaff91c10c1bf4b27774e5f30e7f723a"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b09843123425337d2efee5c8ff6519e4dfc7b044db66c8bd560517fc1070a157"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dca10174a3bd482d969a2d12d0aec2fdd63fb974e255ec0147e36a516a2d68a"}, - {file = "ujson-5.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202ae52f4a53f03c42ead6d046b1a146517e93bd757f517bdeef0a26228e0260"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7a4bed7bd7b288cf73ba47bda27fdd1d78ef6906831489e7f296aef9e786eccb"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d423956f8dfd98a075c9338b886414b6e3c2817dbf67935797466c998af39936"}, - {file = "ujson-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:083c1078e4de3a39019e590c43865b17e07a763fee25b012e650bb4f42c89703"}, - {file = "ujson-5.1.0-cp38-cp38-win32.whl", hash = "sha256:31671ad99f0395eb881d698f2871dc64ff00fbd4380c5d9bfd8bff3d4c8f8d88"}, - {file = "ujson-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:994eaf4369e6bc24258f59fe8c6345037abcf24557571814e27879851c4353aa"}, - {file = "ujson-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00d6ea9702c2eaeaf1a826934eaba1b4c609c873379bf54e36ba7b7e128edf94"}, - {file = "ujson-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a53c4fe8e1c067e6c98b4526e982ed9486f08578ad8eb5f0e94f8cadf0c1d911"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:368f855779fded560724a6448838304621f498113a116d66bc5ed5ad5ad3ca92"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd97e45a0f450ba2c43cda18147e54b8e41e886c22e3506c62f7d61e9e53b0d"}, - {file = "ujson-5.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caeadbf95ce277f1f8f4f71913bc20c01f49fc9228f238920f9ff6f7645d2a5f"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:681fed63c948f757466eeb3aea98873e2ab8b2b18e9020c96a97479a513e2018"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6fc4376266ae67f6d8f9e69386ab950eb84ba345c6fdbeb1884fa5b773c8c76b"}, - {file = "ujson-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:585271d6ad545a2ccfc237582f70c160e627735c89d0ca2bde24afa321bc0750"}, - {file = "ujson-5.1.0-cp39-cp39-win32.whl", hash = "sha256:b631af423e6d5d35f9f37fbcc4fbdb6085abc1c441cf864c64b7fbb5b150faf7"}, - {file = "ujson-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:08265db5ccff8b521ff68aee13a417d68cca784d7e711d961b92fda6ccffcc4f"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2b1c372583eb4363b42e21222d3a18116a41973781d502d61e1b0daf4b8352f"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51142c9d40439f299594e399bef8892a16586ded54c88d3af926865ca221a177"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba8be1717b1867a85b2413a8585bad0e4507a22d6af2c244e1c74151f6d5cc0"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b26d9d6eb9a0979d37f28c715e717a409c9e03163e5cd8fa73aab806351ab5"}, - {file = "ujson-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b2c7e4afde0d36926b091fa9613b18b65e911fcaa60024e8721f2dcfedc25329"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:110633a8dda6c8ca78090292231e15381f8b2423e998399d4bc5f135149c722b"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdac161127ef8e0889180a4c07475457c55fe0bbd644436d8f4c7ef07565d653"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:452990c2b18445a7379a45873527d2ec47789b9289c13a17a3c1cc76b9641126"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5304ad25d100d50b5bc8513ef110335df678f66c7ccf3d4728c0c3aa69e08e0c"}, - {file = "ujson-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ce620a6563b21aa3fbb1658bc1bfddb484a6dad542de1efb5121eb7bb4f2b93a"}, - {file = "ujson-5.1.0.tar.gz", hash = "sha256:a88944d2f99db71a3ca0c63d81f37e55b660edde0b07216fb65a3e46403ef004"}, -] ulid-py = [ {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, {file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"}, @@ -1846,63 +1368,6 @@ urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] -wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, -] -yapf = [ - {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"}, - {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"}, -] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, From 03d08dd16c2d77062c18fbd04fdfab00f4474fb5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:56:58 -0600 Subject: [PATCH 189/365] Add support for message commands in client --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 12b0380..cd9ee8a 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -79,9 +79,9 @@ class Jarvis(Snake): if update["domain"] in self.phishing_domains: self.phishing_domains.remove(update["domain"]) - async def _prerun(self, ctx: InteractionContext, *args, **kwargs) -> None: + async def _prerun(self, ctx: Context, *args, **kwargs) -> None: name = ctx.invoked_name - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") From a038930fd54cce183f9681973a9541003e2bd24e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:58:42 -0600 Subject: [PATCH 190/365] Fix error in log command --- jarvis/cogs/botutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index e897742..65dcb9b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -35,8 +35,8 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: - async for chunk in af.read(8192): - file_bytes.write(chunk) + raw = await af.read() + file_bytes.write(raw) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From ce9dda8239d9a71fda2e48827da796d5bd1d4b96 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:58:57 -0600 Subject: [PATCH 191/365] Remove extra spaces in tail command --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 65dcb9b..312bd4b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -25,7 +25,7 @@ class BotutilCog(Scale): lines.append(line) if len(lines) == count: lines.pop(0) - log = "\n".join(lines) + log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") @message_command(name="log") From 8f0872b30403b0f17a4c59067aa09e0e331cf5ff Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 16:59:57 -0600 Subject: [PATCH 192/365] Read log bytes instead of log string --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 312bd4b..c0a08f6 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -35,7 +35,7 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: - raw = await af.read() + raw = await af.read_bytes() file_bytes.write(raw) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From a4b0ae2b79ee5f76c8885b2aba2f9cbde89b2528 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:01:36 -0600 Subject: [PATCH 193/365] Fix on_command to support message commands --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index cd9ee8a..2fb93d5 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -164,14 +164,14 @@ class Jarvis(Snake): return await super().on_command_error(ctx, error, *args, **kwargs) # Modlog - async def on_command(self, ctx: InteractionContext) -> None: + async def on_command(self, ctx: Context) -> None: """Lepton on_command override.""" if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) args = [] - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") for k, v in ctx.kwargs.items(): if isinstance(v, str): From 08ab641ff18dfdb8553a5cdfe141ef4447081d47 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:02:07 -0600 Subject: [PATCH 194/365] Fix BytesIO reading --- jarvis/cogs/botutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index c0a08f6..23e0739 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -37,6 +37,7 @@ class BotutilCog(Scale): with BytesIO() as file_bytes: raw = await af.read_bytes() file_bytes.write(raw) + file_bytes.seek(0) log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) From 94c1997ab06777ea1f2d541759bf9dbf426ad2fa Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:07:26 -0600 Subject: [PATCH 195/365] Add molter dependency --- jarvis/cogs/botutil.py | 6 ++-- poetry.lock | 73 +++++++++++++----------------------------- pyproject.toml | 2 ++ 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 23e0739..647232d 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -5,7 +5,7 @@ from io import BytesIO from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake from dis_snek.models.discord.file import File -from dis_snek.models.snek.command import message_command +from molter import msg_command class BotutilCog(Scale): @@ -15,7 +15,7 @@ class BotutilCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @message_command(name="tail") + @msg_command(name="tail") async def _tail(self, ctx: MessageContext, count: int = 10) -> None: if ctx.author.id != self.bot.owner.id: return @@ -28,7 +28,7 @@ class BotutilCog(Scale): log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") - @message_command(name="log") + @msg_command(name="log") async def _log(self, ctx: MessageContext) -> None: if ctx.author.id != self.bot.owner.id: return diff --git a/poetry.lock b/poetry.lock index 74b3af0..c1b3a01 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,17 +1,3 @@ -[[package]] -name = "aiofile" -version = "3.7.4" -description = "Asynchronous file operations." -category = "main" -optional = false -python-versions = ">3.4.*, <4" - -[package.dependencies] -caio = ">=0.9.0,<0.10.0" - -[package.extras] -develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] - [[package]] name = "aiohttp" version = "3.8.1" @@ -65,17 +51,6 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] -[[package]] -name = "caio" -version = "0.9.5" -description = "Asynchronous file IO for Linux Posix and Windows." -category = "main" -optional = false -python-versions = ">=3.5.*, <4" - -[package.extras] -develop = ["aiomisc", "pytest", "pytest-cov"] - [[package]] name = "certifi" version = "2021.10.8" @@ -116,7 +91,7 @@ langdetect = ["langdetect"] [[package]] name = "dis-snek" -version = "7.0.0" +version = "8.0.0" description = "An API wrapper for Discord filled with snakes" category = "main" optional = false @@ -128,6 +103,10 @@ attrs = "*" discord-typings = "*" tomli = "*" +[package.extras] +all = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] +voice = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] + [[package]] name = "discord-typings" version = "0.3.1" @@ -217,6 +196,17 @@ docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", " lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] +[[package]] +name = "molter" +version = "0.11.0" +description = "Shedding a new skin on Dis-Snek's commands." +category = "main" +optional = false +python-versions = ">=3.10" + +[package.dependencies] +dis-snek = ">=8.0.0" + [[package]] name = "mongoengine" version = "0.23.1" @@ -619,13 +609,9 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ef0ec99d8c98190d25cda0b8f4420738e569fd73b605dbae2a366a392a9cca7d" +content-hash = "af521f2487cac903d3766516484dcfdf999d222abacb1a8233248489591f6a34" [metadata.files] -aiofile = [ - {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, - {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, -] aiohttp = [ {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, @@ -712,23 +698,6 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] -caio = [ - {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, - {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, - {file = "caio-0.9.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:013aa374158c5074b3c65a0da6b9c6b20a987d85fb317dd077b045e84e2478e1"}, - {file = "caio-0.9.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d767faf537a9ea774e8408ba15a0f1dc734f06857c2d28bdf4258a63b5885f42"}, - {file = "caio-0.9.5-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:97d9a10522a8a25798229fc1113cfaba3832b1cd0c1a3648b009b9740ef5e054"}, - {file = "caio-0.9.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4fe9eff5cf7a2d6f3f418aeeccd11ce9a38329e07527b6f52da085edb44bc2fd"}, - {file = "caio-0.9.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8b6be369139edd678817dc0a313392d710f66fb521c275dce0a9067089b346b"}, - {file = "caio-0.9.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3ffc6259239e03962f9e14829e02795ca9d196eedf32fe61688ba6ed33da46c8"}, - {file = "caio-0.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7a19dfdec6736affb645da233a6007c2590678490d2a1e0f1fb82a696c0a1ddf"}, - {file = "caio-0.9.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15f70d27e1009d279e4f9ff86290aad00b0511ce82a1879c40745244f0a9ec92"}, - {file = "caio-0.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0427a58c1814a091bfbb84318d344fdb9a68f3d49adc74e5fdc7bc9478e1e4fe"}, - {file = "caio-0.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f48fa58e5f699b428f1fd85e394ecec05be4048fcaf1fdf1981b748cd1e03a6"}, - {file = "caio-0.9.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01061288391020f28e1ab8b0437420f7fe1e0ecc29b4107f7a8dcf7789f33b22"}, - {file = "caio-0.9.5-py3-none-any.whl", hash = "sha256:3c74d84dff2bec5f93685cf2f32eb22e4cc5663434a9be5f4a759247229b69b3"}, - {file = "caio-0.9.5.tar.gz", hash = "sha256:167d9342a807bae441b2e88f9ecb62da2f236b319939a8679f68f510a0194c40"}, -] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, @@ -742,8 +711,8 @@ dateparser = [ {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, ] dis-snek = [ - {file = "dis-snek-7.0.0.tar.gz", hash = "sha256:c39d0ff5e1f0cde3a0feefcd05f4a7d6de1d6b1aafbda745bbaa7a63d541af0f"}, - {file = "dis_snek-7.0.0-py3-none-any.whl", hash = "sha256:5a1fa72d3d5de96a7550a480d33a4f4a6ac8509391fa20890c2eb495fb45d221"}, + {file = "dis-snek-8.0.0.tar.gz", hash = "sha256:c035a4f664f9a638b80089f2a9a3330a4254fc227ef2c83c96582df06f392281"}, + {file = "dis_snek-8.0.0-py3-none-any.whl", hash = "sha256:3a89c8f78c27407fb67d42dfaa51be6a507306306779e45cd47687bd846b3b23"}, ] discord-typings = [ {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, @@ -827,6 +796,10 @@ marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] +molter = [ + {file = "molter-0.11.0-py3-none-any.whl", hash = "sha256:4ae311e34fc93bfa37643f86c382b1f104753e451e9904995f0f34f5edda8daa"}, + {file = "molter-0.11.0.tar.gz", hash = "sha256:1e06e021a00986b9218e67bce062cb52eab5c86e8187b28e68f7dca8df853aaa"}, +] mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, diff --git a/pyproject.toml b/pyproject.toml index 3a00631..971dbb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,8 @@ jarvis-core = {git = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-cor aiohttp = "^3.8.1" pastypy = "^1.0.1" dateparser = "^1.1.1" +aiofile = "^3.7.4" +molter = "^0.11.0" [build-system] requires = ["poetry-core>=1.0.0"] From 79a50e9059f1d46dcb338915436c5850d7225957 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:09:20 -0600 Subject: [PATCH 196/365] Fix tail command off-by-1 error --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 647232d..98c961a 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -23,7 +23,7 @@ class BotutilCog(Scale): async with AIOFile("jarvis.log", "r") as af: async for line in LineReader(af): lines.append(line) - if len(lines) == count: + if len(lines) == count + 1: lines.pop(0) log = "".join(lines) await ctx.reply(content=f"```\n{log}\n```") From 3c23802ca002bf2d0f9d3ce36841557c1b1e6fe0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:12:15 -0600 Subject: [PATCH 197/365] Fix error in tail with too large of log --- jarvis/client.py | 2 +- jarvis/cogs/botutil.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 2fb93d5..0f9e956 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -123,7 +123,7 @@ class Jarvis(Snake): timestamp = int(datetime.now(tz=timezone.utc).timestamp()) timestamp = f"" arg_str = "" - if ctx.target_id: + if isinstance(ctx, InteractionContext) and ctx.target_id: ctx.kwargs["context target"] = ctx.target for k, v in ctx.kwargs.items(): arg_str += f" {k}: " diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 98c961a..04f9db0 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -26,7 +26,14 @@ class BotutilCog(Scale): if len(lines) == count + 1: lines.pop(0) log = "".join(lines) - await ctx.reply(content=f"```\n{log}\n```") + if len(log) > 1500: + with BytesIO() as file_bytes: + file_bytes.write(log.encode("UTF8")) + file_bytes.seek(0) + log = File(file_bytes, file_name=f"tail_{count}.log") + await ctx.reply(content=f"Here's the last {count} lines of the log", file=log) + else: + await ctx.reply(content=f"```\n{log}\n```") @msg_command(name="log") async def _log(self, ctx: MessageContext) -> None: From dc01bb9d53f2f84b8b03a1e2ae872c27985740bd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:17:48 -0600 Subject: [PATCH 198/365] Improve message command support in client --- jarvis/client.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 0f9e956..5a5b343 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -19,7 +19,7 @@ from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message -from dis_snek.models.snek.context import Context, InteractionContext +from dis_snek.models.snek.context import Context, InteractionContext, MessageContext from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q @@ -125,11 +125,17 @@ class Jarvis(Snake): arg_str = "" if isinstance(ctx, InteractionContext) and ctx.target_id: ctx.kwargs["context target"] = ctx.target - for k, v in ctx.kwargs.items(): - arg_str += f" {k}: " - if isinstance(v, str) and len(v) > 100: - v = v[97] + "..." - arg_str += f"{v}\n" + if isinstance(ctx, InteractionContext): + for k, v in ctx.kwargs.items(): + arg_str += f" {k}: " + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f"{v}\n" + elif isinstance(ctx, MessageContext): + for v in ctx.args.items(): + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + arg_str += f" - {v}" callback_args = "\n".join(f" - {i}" for i in args) if args else " None" callback_kwargs = ( "\n".join(f" {k}: {v}" for k, v in kwargs.items()) if kwargs else " None" @@ -173,12 +179,18 @@ class Jarvis(Snake): args = [] if isinstance(ctx, InteractionContext) and ctx.target_id: args.append(f"{KEY_FMT}context target:{VAL_FMT}{ctx.target}{RESET}") - for k, v in ctx.kwargs.items(): - if isinstance(v, str): - v = v.replace("`", "\\`") - if len(v) > 100: - v = v[:97] + "..." - args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + if isinstance(ctx, InteractionContext): + for k, v in ctx.kwargs.items(): + if isinstance(v, str): + v = v.replace("`", "\\`") + if len(v) > 100: + v = v[:97] + "..." + args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") + elif isinstance(ctx, MessageContext): + for v in ctx.args.items(): + if isinstance(v, str) and len(v) > 100: + v = v[97] + "..." + args.append(f"{VAL_FMT}{v}{RESET}") args = " ".join(args) fields = [ EmbedField( From f899174fa04c5ab90e87f7626f381f0fd1d8c6f3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:18:27 -0600 Subject: [PATCH 199/365] Fix running .items() on list --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 5a5b343..f054764 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -132,7 +132,7 @@ class Jarvis(Snake): v = v[97] + "..." arg_str += f"{v}\n" elif isinstance(ctx, MessageContext): - for v in ctx.args.items(): + for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." arg_str += f" - {v}" @@ -187,7 +187,7 @@ class Jarvis(Snake): v = v[:97] + "..." args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") elif isinstance(ctx, MessageContext): - for v in ctx.args.items(): + for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." args.append(f"{VAL_FMT}{v}{RESET}") From 820ff56255a8dfc72316bbd16a86ee197c85f0a0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:37:36 -0600 Subject: [PATCH 200/365] Rename J.A.R.V.I.S. -> JARVIS --- jarvis/__init__.py | 4 ++-- jarvis/cogs/admin/__init__.py | 4 ++-- jarvis/cogs/admin/ban.py | 4 ++-- jarvis/cogs/admin/kick.py | 4 ++-- jarvis/cogs/admin/lock.py | 4 ++-- jarvis/cogs/admin/lockdown.py | 4 ++-- jarvis/cogs/admin/mute.py | 4 ++-- jarvis/cogs/admin/purge.py | 4 ++-- jarvis/cogs/admin/roleping.py | 4 ++-- jarvis/cogs/admin/warning.py | 4 ++-- jarvis/cogs/autoreact.py | 6 +++--- jarvis/cogs/botutil.py | 2 +- jarvis/cogs/ctc2.py | 6 +++--- jarvis/cogs/dbrand.py | 6 +++--- jarvis/cogs/dev.py | 8 ++++---- jarvis/cogs/gl.py | 16 ++++++++-------- jarvis/cogs/image.py | 6 +++--- jarvis/cogs/remindme.py | 6 +++--- jarvis/cogs/rolegiver.py | 6 +++--- jarvis/cogs/settings.py | 6 +++--- jarvis/cogs/starboard.py | 6 +++--- jarvis/cogs/twitter.py | 6 +++--- jarvis/cogs/util.py | 10 +++++----- jarvis/cogs/verify.py | 6 +++--- jarvis/config.py | 4 ++-- jarvis/utils/__init__.py | 8 ++++---- jarvis/utils/permissions.py | 2 +- 27 files changed, 75 insertions(+), 75 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index fc18c43..2b0804c 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,4 +1,4 @@ -"""Main J.A.R.V.I.S. package.""" +"""Main JARVIS package.""" import logging from importlib.metadata import version as _v @@ -36,7 +36,7 @@ jarvis = Jarvis( async def run() -> None: - """Run J.A.R.V.I.S.""" + """Run JARVIS""" logger.info("Starting JARVIS") logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index 2022554..f0fb1af 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Admin Cogs.""" +"""JARVIS Admin Cogs.""" import logging from dis_snek import Snake @@ -7,7 +7,7 @@ from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, def setup(bot: Snake) -> None: - """Add admin cogs to J.A.R.V.I.S.""" + """Add admin cogs to JARVIS""" logger = logging.getLogger(__name__) msg = "Loaded jarvis.cogs.admin.{}" ban.BanCog(bot) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 242c624..223b37e 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. BanCog.""" +"""JARVIS BanCog.""" import logging import re @@ -24,7 +24,7 @@ from jarvis.utils.permissions import admin_or_permissions class BanCog(ModcaseCog): - """J.A.R.V.I.S. BanCog.""" + """JARVIS BanCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 4175987..618e61b 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. KickCog.""" +"""JARVIS KickCog.""" import logging from dis_snek import InteractionContext, Permissions, Snake @@ -18,7 +18,7 @@ from jarvis.utils.permissions import admin_or_permissions class KickCog(ModcaseCog): - """J.A.R.V.I.S. KickCog.""" + """JARVIS KickCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index 7c8f4a2..fb18a8b 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. LockCog.""" +"""JARVIS LockCog.""" import logging from typing import Union @@ -19,7 +19,7 @@ from jarvis.utils.permissions import admin_or_permissions class LockCog(Scale): - """J.A.R.V.I.S. LockCog.""" + """JARVIS LockCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index 527bfa5..8ee7ac5 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. LockdownCog.""" +"""JARVIS LockdownCog.""" import logging from dis_snek import InteractionContext, Scale, Snake @@ -93,7 +93,7 @@ async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: class LockdownCog(Scale): - """J.A.R.V.I.S. LockdownCog.""" + """JARVIS LockdownCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index 3ad3a56..a8df321 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. MuteCog.""" +"""JARVIS MuteCog.""" import asyncio import logging from datetime import datetime, timedelta, timezone @@ -26,7 +26,7 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): - """J.A.R.V.I.S. MuteCog.""" + """JARVIS MuteCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index b8d6f4a..4036eb9 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. PurgeCog.""" +"""JARVIS PurgeCog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -16,7 +16,7 @@ from jarvis.utils.permissions import admin_or_permissions class PurgeCog(Scale): - """J.A.R.V.I.S. PurgeCog.""" + """JARVIS PurgeCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 7f62bf7..6fda235 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. RolepingCog.""" +"""JARVIS RolepingCog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class RolepingCog(Scale): - """J.A.R.V.I.S. RolepingCog.""" + """JARVIS RolepingCog.""" def __init__(self, bot: Snake): self.bot = bot diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 031ee86..97e2f7b 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. WarningCog.""" +"""JARVIS WarningCog.""" import logging from dis_snek import InteractionContext, Permissions, Snake @@ -22,7 +22,7 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(ModcaseCog): - """J.A.R.V.I.S. WarningCog.""" + """JARVIS WarningCog.""" def __init__(self, bot: Snake): super().__init__(bot) diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index 9d421f2..db1a41f 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Autoreact Cog.""" +"""JARVIS Autoreact Cog.""" import logging import re from typing import Optional, Tuple @@ -20,7 +20,7 @@ from jarvis.utils.permissions import admin_or_permissions class AutoReactCog(Scale): - """J.A.R.V.I.S. Autoreact Cog.""" + """JARVIS Autoreact Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -207,5 +207,5 @@ class AutoReactCog(Scale): def setup(bot: Snake) -> None: - """Add AutoReactCog to J.A.R.V.I.S.""" + """Add AutoReactCog to JARVIS""" AutoReactCog(bot) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 04f9db0..2341c0e 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -50,5 +50,5 @@ class BotutilCog(Scale): def setup(bot: Snake) -> None: - """Add BotutilCog to J.A.R.V.I.S.""" + """Add BotutilCog to JARVIS""" BotutilCog(bot) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index ee061e0..90519dc 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Complete the Code 2 Cog.""" +"""JARVIS Complete the Code 2 Cog.""" import logging import re @@ -30,7 +30,7 @@ invites = re.compile( class CTCCog(Scale): - """J.A.R.V.I.S. Complete the Code 2 Cog.""" + """JARVIS Complete the Code 2 Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -149,5 +149,5 @@ class CTCCog(Scale): def setup(bot: Snake) -> None: - """Add CTCCog to J.A.R.V.I.S.""" + """Add CTCCog to JARVIS""" CTCCog(bot) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 5cea582..8fe1c98 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. dbrand cog.""" +"""JARVIS dbrand cog.""" import logging import re @@ -22,7 +22,7 @@ guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862 class DbrandCog(Scale): """ - dbrand functions for J.A.R.V.I.S. + dbrand functions for JARVIS Mostly support functions. Credit @cpixl for the shipping API """ @@ -198,5 +198,5 @@ class DbrandCog(Scale): def setup(bot: Snake) -> None: - """Add dbrandcog to J.A.R.V.I.S.""" + """Add dbrandcog to JARVIS""" DbrandCog(bot) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index c7b5e6d..4d2b743 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Developer Cog.""" +"""JARVIS Developer Cog.""" import base64 import hashlib import logging @@ -46,7 +46,7 @@ MAX_FILESIZE = 5 * (1024**3) # 5GB class DevCog(Scale): - """J.A.R.V.I.S. Developer Cog.""" + """JARVIS Developer Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -266,7 +266,7 @@ class DevCog(Scale): embed = build_embed(title="Decoded Data", description="", fields=fields) await ctx.send(embed=embed) - @slash_command(name="cloc", description="Get J.A.R.V.I.S. lines of code") + @slash_command(name="cloc", description="Get JARVIS lines of code") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _cloc(self, ctx: InteractionContext) -> None: output = subprocess.check_output( # noqa: S603, S607 @@ -276,5 +276,5 @@ class DevCog(Scale): def setup(bot: Snake) -> None: - """Add DevCog to J.A.R.V.I.S.""" + """Add DevCog to JARVIS""" DevCog(bot) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 80c6a31..7fa80b2 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. GitLab Cog.""" +"""JARVIS GitLab Cog.""" import asyncio import logging from datetime import datetime @@ -26,14 +26,14 @@ guild_ids = [862402786116763668] class GitlabCog(Scale): - """J.A.R.V.I.S. GitLab Cog.""" + """JARVIS GitLab Cog.""" def __init__(self, bot: Snake): self.bot = bot self.logger = logging.getLogger(__name__) config = JarvisConfig.from_yaml() self._gitlab = gitlab.Gitlab("https://git.zevaryx.com", private_token=config.gitlab_token) - # J.A.R.V.I.S. GitLab ID is 29 + # JARVIS GitLab ID is 29 self.project = self._gitlab.projects.get(29) gl = SlashCommand(name="gl", description="Get GitLab info", scopes=guild_ids) @@ -149,7 +149,7 @@ class GitlabCog(Scale): url=milestone.web_url, ) embed.set_author( - name="J.A.R.V.I.S.", + name="JARVIS", url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) @@ -240,7 +240,7 @@ class GitlabCog(Scale): title = "" if t_state: title = f"{t_state} " - title += f"J.A.R.V.I.S. {name}s" + title += f"JARVIS {name}s" fields = [] for item in api_list: description = item.description or "No description" @@ -256,10 +256,10 @@ class GitlabCog(Scale): title=title, description="", fields=fields, - url=f"https://git.zevaryx.com/stark-industries/j.a.r.v.i.s./{name.replace(' ', '_')}s", + url=f"https://git.zevaryx.com/stark-industries/JARVIS/{name.replace(' ', '_')}s", ) embed.set_author( - name="J.A.R.V.I.S.", + name="JARVIS", url="https://git.zevaryx.com/jarvis", icon_url="https://git.zevaryx.com/uploads/-/system/user/avatar/11/avatar.png", ) @@ -464,6 +464,6 @@ class GitlabCog(Scale): def setup(bot: Snake) -> None: - """Add GitlabCog to J.A.R.V.I.S. if Gitlab token exists.""" + """Add GitlabCog to JARVIS if Gitlab token exists.""" if JarvisConfig.from_yaml().gitlab_token: GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index bcd57db..0dac7f8 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. image processing cog.""" +"""JARVIS image processing cog.""" import logging import re from io import BytesIO @@ -22,7 +22,7 @@ MIN_ACCURACY = 0.80 class ImageCog(Scale): """ - Image processing functions for J.A.R.V.I.S. + Image processing functions for JARVIS May be categorized under util later """ @@ -152,5 +152,5 @@ class ImageCog(Scale): def setup(bot: Snake) -> None: - """Add ImageCog to J.A.R.V.I.S.""" + """Add ImageCog to JARVIS""" ImageCog(bot) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 9623d5a..7225412 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Remind Me Cog.""" +"""JARVIS Remind Me Cog.""" import asyncio import logging import re @@ -34,7 +34,7 @@ invites = re.compile( class RemindmeCog(Scale): - """J.A.R.V.I.S. Remind Me Cog.""" + """JARVIS Remind Me Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -337,5 +337,5 @@ class RemindmeCog(Scale): def setup(bot: Snake) -> None: - """Add RemindmeCog to J.A.R.V.I.S.""" + """Add RemindmeCog to JARVIS""" RemindmeCog(bot) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index e159828..d934de8 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Role Giver Cog.""" +"""JARVIS Role Giver Cog.""" import asyncio import logging @@ -22,7 +22,7 @@ from jarvis.utils.permissions import admin_or_permissions class RolegiverCog(Scale): - """J.A.R.V.I.S. Role Giver Cog.""" + """JARVIS Role Giver Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -379,5 +379,5 @@ class RolegiverCog(Scale): def setup(bot: Snake) -> None: - """Add RolegiverCog to J.A.R.V.I.S.""" + """Add RolegiverCog to JARVIS""" RolegiverCog(bot) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index c471cc2..d5e5086 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Settings Management Cog.""" +"""JARVIS Settings Management Cog.""" import asyncio import logging from typing import Any @@ -23,7 +23,7 @@ from jarvis.utils.permissions import admin_or_permissions class SettingsCog(Scale): - """J.A.R.V.I.S. Settings Management Cog.""" + """JARVIS Settings Management Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -270,5 +270,5 @@ class SettingsCog(Scale): def setup(bot: Snake) -> None: - """Add SettingsCog to J.A.R.V.I.S.""" + """Add SettingsCog to JARVIS""" SettingsCog(bot) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index e285b4e..85383c7 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Starboard Cog.""" +"""JARVIS Starboard Cog.""" import logging from dis_snek import InteractionContext, Permissions, Scale, Snake @@ -29,7 +29,7 @@ supported_images = [ class StarboardCog(Scale): - """J.A.R.V.I.S. Starboard Cog.""" + """JARVIS Starboard Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -319,5 +319,5 @@ class StarboardCog(Scale): def setup(bot: Snake) -> None: - """Add StarboardCog to J.A.R.V.I.S.""" + """Add StarboardCog to JARVIS""" StarboardCog(bot) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 6bc5416..a043d16 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Twitter Cog.""" +"""JARVIS Twitter Cog.""" import asyncio import logging @@ -21,7 +21,7 @@ from jarvis.utils.permissions import admin_or_permissions class TwitterCog(Scale): - """J.A.R.V.I.S. Twitter Cog.""" + """JARVIS Twitter Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -244,5 +244,5 @@ class TwitterCog(Scale): def setup(bot: Snake) -> None: - """Add TwitterCog to J.A.R.V.I.S.""" + """Add TwitterCog to JARVIS""" TwitterCog(bot) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 0976a54..ff5d7b7 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Utility Cog.""" +"""JARVIS Utility Cog.""" import logging import platform import re @@ -39,7 +39,7 @@ JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA") class UtilCog(Scale): """ - Utility functions for J.A.R.V.I.S. + Utility functions for JARVIS Mostly system utility functions, but may change over time """ @@ -48,10 +48,10 @@ class UtilCog(Scale): self.bot = bot self.logger = logging.getLogger(__name__) - @slash_command(name="status", description="Retrieve J.A.R.V.I.S. status") + @slash_command(name="status", description="Retrieve JARVIS status") @cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30) async def _status(self, ctx: InteractionContext) -> None: - title = "J.A.R.V.I.S. Status" + title = "JARVIS Status" desc = f"All systems online\nConnected to **{len(self.bot.guilds)}** guilds" color = "#3498db" fields = [] @@ -376,5 +376,5 @@ class UtilCog(Scale): def setup(bot: Snake) -> None: - """Add UtilCog to J.A.R.V.I.S.""" + """Add UtilCog to JARVIS""" UtilCog(bot) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index f3cbe29..602f691 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Verify Cog.""" +"""JARVIS Verify Cog.""" import asyncio import logging from random import randint @@ -31,7 +31,7 @@ def create_layout() -> list: class VerifyCog(Scale): - """J.A.R.V.I.S. Verify Cog.""" + """JARVIS Verify Cog.""" def __init__(self, bot: Snake): self.bot = bot @@ -95,5 +95,5 @@ class VerifyCog(Scale): def setup(bot: Snake) -> None: - """Add VerifyCog to J.A.R.V.I.S.""" + """Add VerifyCog to JARVIS""" VerifyCog(bot) diff --git a/jarvis/config.py b/jarvis/config.py index 3ab039e..2aec603 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,4 +1,4 @@ -"""Load the config for J.A.R.V.I.S.""" +"""Load the config for JARVIS""" import os from jarvis_core.config import Config as CConfig @@ -25,7 +25,7 @@ class JarvisConfig(CConfig): class Config(object): - """Config singleton object for J.A.R.V.I.S.""" + """Config singleton object for JARVIS""" def __new__(cls, *args: list, **kwargs: dict): """Get the singleton config, or creates a new one.""" diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 3c5de79..751bfe6 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,4 +1,4 @@ -"""J.A.R.V.I.S. Utility Functions.""" +"""JARVIS Utility Functions.""" from datetime import datetime, timezone from pkgutil import iter_modules @@ -65,14 +65,14 @@ def modlog_embed( def get_extensions(path: str = jarvis.cogs.__path__) -> list: - """Get J.A.R.V.I.S. cogs.""" + """Get JARVIS cogs.""" config = get_config() vals = config.cogs or [x.name for x in iter_modules(path)] return ["jarvis.cogs.{}".format(x) for x in vals] def update() -> int: - """J.A.R.V.I.S. update utility.""" + """JARVIS update utility.""" repo = git.Repo(".") dirty = repo.is_dirty() current_hash = repo.head.object.hexsha @@ -87,6 +87,6 @@ def update() -> int: def get_repo_hash() -> str: - """J.A.R.V.I.S. current branch hash.""" + """JARVIS current branch hash.""" repo = git.Repo(".") return repo.head.object.hexsha diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index a20b686..1eacbc7 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -5,7 +5,7 @@ from jarvis.config import get_config def user_is_bot_admin() -> bool: - """Check if a user is a J.A.R.V.I.S. admin.""" + """Check if a user is a JARVIS admin.""" async def predicate(ctx: InteractionContext) -> bool: """Command check predicate.""" From 295677fbedcadf9bf79a09fc4aa2ee11d804c205 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:57:03 -0600 Subject: [PATCH 201/365] Bump version to v2.0.0b2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 971dbb8..247125e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jarvis" -version = "2.0.0b1" +version = "2.0.0b2" description = "J.A.R.V.I.S. admin bot" authors = ["Zevaryx "] From bbb12b1e29206fcd3ab7402d85fa14649f60ba73 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 18:13:56 -0600 Subject: [PATCH 202/365] Add jarvis.const to avoid potential future circular import --- jarvis/__init__.py | 8 ++------ jarvis/client.py | 2 ++ jarvis/cogs/util.py | 4 ++-- jarvis/const.py | 7 +++++++ 4 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 jarvis/const.py diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 2b0804c..009750e 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,19 +1,15 @@ """Main JARVIS package.""" import logging -from importlib.metadata import version as _v from dis_snek import Intents from jarvis_core.db import connect from jarvis_core.log import get_logger -from jarvis import utils +from jarvis import const, utils from jarvis.client import Jarvis from jarvis.config import JarvisConfig -try: - __version__ = _v("jarvis") -except Exception: - __version__ = "0.0.0" +__version__ = const.__version__ jconfig = JarvisConfig.from_yaml() logger = get_logger("jarvis") diff --git a/jarvis/client.py b/jarvis/client.py index f054764..3829b03 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -29,6 +29,7 @@ from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste +from jarvis import const from jarvis.utils.embeds import warning_embed DEFAULT_GUILD = 862402786116763668 @@ -101,6 +102,7 @@ class Jarvis(Snake): self._update_domains.start() self.logger.info("Logged in as {}".format(self.user)) # noqa: T001 self.logger.info("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 + self.logger.info("Current version: {}".format(const.__version__)) self.logger.info( # noqa: T001 "https://discord.com/api/oauth2/authorize?client_id=" "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index ff5d7b7..728a9d3 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -29,7 +29,7 @@ from dis_snek.models.snek.cooldowns import Buckets from PIL import Image from tzlocal import get_localzone -import jarvis +from jarvis import const as jconst from jarvis.data import pigpen from jarvis.data.robotcamo import emotes, hk, names from jarvis.utils import build_embed, get_repo_hash @@ -57,7 +57,7 @@ class UtilCog(Scale): fields = [] fields.append(EmbedField(name="dis-snek", value=const.__version__)) - fields.append(EmbedField(name="Version", value=jarvis.__version__, inline=False)) + fields.append(EmbedField(name="Version", value=jconst.__version__, inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) num_domains = len(self.bot.phishing_domains) fields.append( diff --git a/jarvis/const.py b/jarvis/const.py new file mode 100644 index 0000000..d1f2869 --- /dev/null +++ b/jarvis/const.py @@ -0,0 +1,7 @@ +"""JARVIS constants.""" +from importlib.metadata import version as _v + +try: + __version__ = _v("jarvis") +except Exception: + __version__ = "0.0.0" From 12dfe8ab58ac82f026d5c5a055bff2d9b249c43c Mon Sep 17 00:00:00 2001 From: zevaryx Date: Tue, 19 Apr 2022 18:14:54 -0600 Subject: [PATCH 203/365] Update poetry.lock --- poetry.lock | 253 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 153 insertions(+), 100 deletions(-) diff --git a/poetry.lock b/poetry.lock index c1b3a01..86c0f8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,17 @@ +[[package]] +name = "aiofile" +version = "3.7.4" +description = "Asynchronous file operations." +category = "main" +optional = false +python-versions = ">3.4.*, <4" + +[package.dependencies] +caio = ">=0.9.0,<0.10.0" + +[package.extras] +develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] + [[package]] name = "aiohttp" version = "3.8.1" @@ -51,6 +65,17 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +[[package]] +name = "caio" +version = "0.9.5" +description = "Asynchronous file IO for Linux Posix and Windows." +category = "main" +optional = false +python-versions = ">=3.5.*, <4" + +[package.extras] +develop = ["aiomisc", "pytest", "pytest-cov"] + [[package]] name = "certifi" version = "2021.10.8" @@ -109,7 +134,7 @@ voice = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] [[package]] name = "discord-typings" -version = "0.3.1" +version = "0.4.0" description = "Maintained typings of payloads that Discord sends" category = "main" optional = false @@ -279,7 +304,7 @@ numpy = [ [[package]] name = "orjson" -version = "3.6.7" +version = "3.6.8" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" category = "main" optional = false @@ -298,8 +323,8 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pastypy" -version = "1.0.1" -description = "" +version = "1.0.2" +description = "Pasty API wrapper" category = "main" optional = false python-versions = ">=3.10" @@ -308,11 +333,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -321,12 +346,16 @@ yarl = {version = "1.7.2", markers = "python_version >= \"3.6\""} [[package]] name = "pillow" -version = "9.0.1" +version = "9.1.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "psutil" version = "5.9.0" @@ -366,14 +395,14 @@ zstd = ["zstandard"] [[package]] name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "python-dateutil" @@ -388,7 +417,7 @@ six = ">=1.5" [[package]] name = "python-gitlab" -version = "3.2.0" +version = "3.3.0" description = "Interact with GitLab API" category = "main" optional = false @@ -507,7 +536,7 @@ python-versions = ">=3.7" [[package]] name = "tweepy" -version = "4.7.0" +version = "4.8.0" description = "Twitter library for Python" category = "main" optional = false @@ -526,11 +555,11 @@ test = ["vcrpy (>=1.10.3)"] [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tzdata" @@ -542,7 +571,7 @@ python-versions = ">=2" [[package]] name = "tzlocal" -version = "4.1" +version = "4.2" description = "tzinfo object for the local timezone" category = "main" optional = false @@ -609,9 +638,13 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "af521f2487cac903d3766516484dcfdf999d222abacb1a8233248489591f6a34" +content-hash = "ed0f4a3c6383dfad0b1aa4c2a45e14283972734400b9b29095a04dfb7d14eb7b" [metadata.files] +aiofile = [ + {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, + {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, +] aiohttp = [ {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, @@ -698,6 +731,23 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] +caio = [ + {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, + {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, + {file = "caio-0.9.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:013aa374158c5074b3c65a0da6b9c6b20a987d85fb317dd077b045e84e2478e1"}, + {file = "caio-0.9.5-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d767faf537a9ea774e8408ba15a0f1dc734f06857c2d28bdf4258a63b5885f42"}, + {file = "caio-0.9.5-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:97d9a10522a8a25798229fc1113cfaba3832b1cd0c1a3648b009b9740ef5e054"}, + {file = "caio-0.9.5-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4fe9eff5cf7a2d6f3f418aeeccd11ce9a38329e07527b6f52da085edb44bc2fd"}, + {file = "caio-0.9.5-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8b6be369139edd678817dc0a313392d710f66fb521c275dce0a9067089b346b"}, + {file = "caio-0.9.5-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3ffc6259239e03962f9e14829e02795ca9d196eedf32fe61688ba6ed33da46c8"}, + {file = "caio-0.9.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7a19dfdec6736affb645da233a6007c2590678490d2a1e0f1fb82a696c0a1ddf"}, + {file = "caio-0.9.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15f70d27e1009d279e4f9ff86290aad00b0511ce82a1879c40745244f0a9ec92"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0427a58c1814a091bfbb84318d344fdb9a68f3d49adc74e5fdc7bc9478e1e4fe"}, + {file = "caio-0.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f48fa58e5f699b428f1fd85e394ecec05be4048fcaf1fdf1981b748cd1e03a6"}, + {file = "caio-0.9.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01061288391020f28e1ab8b0437420f7fe1e0ecc29b4107f7a8dcf7789f33b22"}, + {file = "caio-0.9.5-py3-none-any.whl", hash = "sha256:3c74d84dff2bec5f93685cf2f32eb22e4cc5663434a9be5f4a759247229b69b3"}, + {file = "caio-0.9.5.tar.gz", hash = "sha256:167d9342a807bae441b2e88f9ecb62da2f236b319939a8679f68f510a0194c40"}, +] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, @@ -715,8 +765,8 @@ dis-snek = [ {file = "dis_snek-8.0.0-py3-none-any.whl", hash = "sha256:3a89c8f78c27407fb67d42dfaa51be6a507306306779e45cd47687bd846b3b23"}, ] discord-typings = [ - {file = "discord-typings-0.3.1.tar.gz", hash = "sha256:854cfb66d34edad49b36d8aaffc93179bb397a97c81caba2da02896e72821a74"}, - {file = "discord_typings-0.3.1-py3-none-any.whl", hash = "sha256:65890c467751daa025dcef15683c32160f07427baf83380cfdf11d84ceec980a"}, + {file = "discord-typings-0.4.0.tar.gz", hash = "sha256:66bce666194e8f006914f788f940265c009cce9b63f9a8ce2bc7931d3d3ef11c"}, + {file = "discord_typings-0.4.0-py3-none-any.whl", hash = "sha256:a390b614cbcbd82af083660c12e46536b4b790ac026f8d43bdd11c8953837ca0"}, ] frozenlist = [ {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, @@ -905,83 +955,86 @@ opencv-python = [ {file = "opencv_python-4.5.5.64-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7787bb017ae93d5f9bb1b817ac8e13e45dd193743cb648498fcab21d00cf20a3"}, ] orjson = [ - {file = "orjson-3.6.7-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93188a9d6eb566419ad48befa202dfe7cd7a161756444b99c4ec77faea9352a4"}, - {file = "orjson-3.6.7-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82515226ecb77689a029061552b5df1802b75d861780c401e96ca6bc8495f775"}, - {file = "orjson-3.6.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3af57ffab7848aaec6ba6b9e9b41331250b57bf696f9d502bacdc71a0ebab0ba"}, - {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:a7297504d1142e7efa236ffc53f056d73934a993a08646dbcee89fc4308a8fcf"}, - {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:5a50cde0dbbde255ce751fd1bca39d00ecd878ba0903c0480961b31984f2fab7"}, - {file = "orjson-3.6.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d21f9a2d1c30e58070f93988db4cad154b9009fafbde238b52c1c760e3607fbe"}, - {file = "orjson-3.6.7-cp310-none-win_amd64.whl", hash = "sha256:e152464c4606b49398afd911777decebcf9749cc8810c5b4199039e1afb0991e"}, - {file = "orjson-3.6.7-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:0a65f3c403f38b0117c6dd8e76e85a7bd51fcd92f06c5598dfeddbc44697d3e5"}, - {file = "orjson-3.6.7-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c47cfca18e41f7f37b08ff3e7abf5ada2d0f27b5ade934f05be5fc5bb956e9d"}, - {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63185af814c243fad7a72441e5f98120c9ecddf2675befa486d669fb65539e9b"}, - {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2da6fde42182b80b40df2e6ab855c55090ebfa3fcc21c182b7ad1762b61d55c"}, - {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:48c5831ec388b4e2682d4ff56d6bfa4a2ef76c963f5e75f4ff4785f9cf338a80"}, - {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:913fac5d594ccabf5e8fbac15b9b3bb9c576d537d49eeec9f664e7a64dde4c4b"}, - {file = "orjson-3.6.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:58f244775f20476e5851e7546df109f75160a5178d44257d437ba6d7e562bfe8"}, - {file = "orjson-3.6.7-cp37-none-win_amd64.whl", hash = "sha256:2d5f45c6b85e5f14646df2d32ecd7ff20fcccc71c0ea1155f4d3df8c5299bbb7"}, - {file = "orjson-3.6.7-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:612d242493afeeb2068bc72ff2544aa3b1e627578fcf92edee9daebb5893ffea"}, - {file = "orjson-3.6.7-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:539cdc5067db38db27985e257772d073cd2eb9462d0a41bde96da4e4e60bd99b"}, - {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d103b721bbc4f5703f62b3882e638c0b65fcdd48622531c7ffd45047ef8e87c"}, - {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb10a20f80e95102dd35dfbc3a22531661b44a09b55236b012a446955846b023"}, - {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:bb68d0da349cf8a68971a48ad179434f75256159fe8b0715275d9b49fa23b7a3"}, - {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:4a2c7d0a236aaeab7f69c17b7ab4c078874e817da1bfbb9827cb8c73058b3050"}, - {file = "orjson-3.6.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3be045ca3b96119f592904cf34b962969ce97bd7843cbfca084009f6c8d2f268"}, - {file = "orjson-3.6.7-cp38-none-win_amd64.whl", hash = "sha256:bd765c06c359d8a814b90f948538f957fa8a1f55ad1aaffcdc5771996aaea061"}, - {file = "orjson-3.6.7-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7dd9e1e46c0776eee9e0649e3ae9584ea368d96851bcaeba18e217fa5d755283"}, - {file = "orjson-3.6.7-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c4b4f20a1e3df7e7c83717aff0ef4ab69e42ce2fb1f5234682f618153c458406"}, - {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7107a5673fd0b05adbb58bf71c1578fc84d662d29c096eb6d998982c8635c221"}, - {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08b6940dd9a98ccf09785890112a0f81eadb4f35b51b9a80736d1725437e22c"}, - {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:f5d1648e5a9d1070f3628a69a7c6c17634dbb0caf22f2085eca6910f7427bf1f"}, - {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e6201494e8dff2ce7fd21da4e3f6dfca1a3fed38f9dcefc972f552f6596a7621"}, - {file = "orjson-3.6.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70d0386abe02879ebaead2f9632dd2acb71000b4721fd8c1a2fb8c031a38d4d5"}, - {file = "orjson-3.6.7-cp39-none-win_amd64.whl", hash = "sha256:d9a3288861bfd26f3511fb4081561ca768674612bac59513cb9081bb61fcc87f"}, - {file = "orjson-3.6.7.tar.gz", hash = "sha256:a4bb62b11289b7620eead2f25695212e9ac77fcfba76f050fa8a540fb5c32401"}, + {file = "orjson-3.6.8-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:3a287a650458de2211db03681b71c3e5cb2212b62f17a39df8ad99fc54855d0f"}, + {file = "orjson-3.6.8-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:5204e25c12cea58e524fc82f7c27ed0586f592f777b33075a92ab7b3eb3687c2"}, + {file = "orjson-3.6.8-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77e8386393add64f959c044e0fb682364fd0e611a6f477aa13f0e6a733bd6a28"}, + {file = "orjson-3.6.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:279f2d2af393fdf8601020744cb206b91b54ad60fb8401e0761819c7bda1f4e4"}, + {file = "orjson-3.6.8-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:c31c9f389be7906f978ed4192eb58a4b74a37ad60556a0b88ddc47c576697770"}, + {file = "orjson-3.6.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0db5c5a0c5b89f092d52f6e5a3701660a9d6ffa9e2968b3ce17c2bc4f5eb0414"}, + {file = "orjson-3.6.8-cp310-none-win_amd64.whl", hash = "sha256:eb22485847b9a0c4bbedc668df860126ac931edbed1d456cf41a59f3cb961ed8"}, + {file = "orjson-3.6.8-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:1a5fe569310bc819279bd4d5f2c349910b104ed3207936246dd5d5e0b085e74a"}, + {file = "orjson-3.6.8-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ccb356a47ab1067cd3549847e9db1d279a63fe0482d315b3ffd6e7abef35ef77"}, + {file = "orjson-3.6.8-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab29c069c222248ce302a25855b4e1664f9436e8ae5a131fb0859daf31676d2b"}, + {file = "orjson-3.6.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d2b5e4cba9e774ac011071d9d27760f97f4b8cd46003e971d122e712f971345"}, + {file = "orjson-3.6.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:c311ec504414d22834d5b972a209619925b48263856a11a14d90230f9682d49c"}, + {file = "orjson-3.6.8-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:a3dfec7950b90fb8d143743503ee53fa06b32e6068bdea792fc866284da3d71d"}, + {file = "orjson-3.6.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b890dbbada2cbb26eb29bd43a848426f007f094bb0758df10dfe7a438e1cb4b4"}, + {file = "orjson-3.6.8-cp37-none-win_amd64.whl", hash = "sha256:9143ae2c52771525be9ad11a7a8cc8e7fd75391b107e7e644a9e0050496f6b4f"}, + {file = "orjson-3.6.8-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:33a82199fd42f6436f833e210ae5129c922a5c355629356ca7a8e82964da7285"}, + {file = "orjson-3.6.8-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:90159ea8b9a5a2a98fa33dc7b421cfac4d2ae91ba5e1058f5909e7f059f6b467"}, + {file = "orjson-3.6.8-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:656fbe15d9ef0733e740d9def78f4fdb4153102f4836ee774a05123499005931"}, + {file = "orjson-3.6.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7be3be6153843e0f01351b1313a5ad4723595427680dac2dfff22a37e652ce02"}, + {file = "orjson-3.6.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:dd24f66b6697ee7424f7da575ec6cbffc8ede441114d53470949cda4d97c6e56"}, + {file = "orjson-3.6.8-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:b07c780f7345ecf5901356dc21dee0669defc489c38ce7b9ab0f5e008cc0385c"}, + {file = "orjson-3.6.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ea32015a5d8a4ce00d348a0de5dc7040e0ad58f970a8fcbb5713a1eac129e493"}, + {file = "orjson-3.6.8-cp38-none-win_amd64.whl", hash = "sha256:c5a3e382194c838988ec128a26b08aa92044e5e055491cc4056142af0c1c54d7"}, + {file = "orjson-3.6.8-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:83a8424e857ae1bf53530e88b4eb2f16ca2b489073b924e655f1575cacd7f52a"}, + {file = "orjson-3.6.8-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:81e1a6a2d67f15007dadacbf9ba5d3d79237e5e33786c028557fe5a2b72f1c9a"}, + {file = "orjson-3.6.8-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:137b539881c77866eba86ff6a11df910daf2eb9ab8f1acae62f879e83d7c38af"}, + {file = "orjson-3.6.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cbd358f3b3ad539a27e36900e8e7d172d0e1b72ad9dd7d69544dcbc0f067ee7"}, + {file = "orjson-3.6.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:6ab94701542d40b90903ecfc339333f458884979a01cb9268bc662cc67a5f6d8"}, + {file = "orjson-3.6.8-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:32b6f26593a9eb606b40775826beb0dac152e3d224ea393688fced036045a821"}, + {file = "orjson-3.6.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:afd9e329ebd3418cac3cd747769b1d52daa25fa672bbf414ab59f0e0881b32b9"}, + {file = "orjson-3.6.8-cp39-none-win_amd64.whl", hash = "sha256:0c89b419914d3d1f65a1b0883f377abe42a6e44f6624ba1c63e8846cbfc2fa60"}, + {file = "orjson-3.6.8.tar.gz", hash = "sha256:e19d23741c5de13689bb316abfccea15a19c264e3ec8eb332a5319a583595ace"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pastypy = [ - {file = "pastypy-1.0.1-py3-none-any.whl", hash = "sha256:63cc664568f86f6ddeb7e5687422bbf4b338d067ea887ed240223c8cbcf6fd2d"}, - {file = "pastypy-1.0.1.tar.gz", hash = "sha256:0393d1635b5031170eae3efaf376b14c3a4af7737c778d7ba7d56f2bd25bf5b1"}, + {file = "pastypy-1.0.2-py3-none-any.whl", hash = "sha256:4476e47b5e52600a4d69c58cbbba2c5d42458f552ccfc2854d5fe97a119dcc20"}, + {file = "pastypy-1.0.2.tar.gz", hash = "sha256:81e0c4a65ec40c85d62685627b64d26397304ac91d68ddc80f833974504c13b8"}, ] pillow = [ - {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, - {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, - {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, - {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, - {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, - {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, - {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, - {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, - {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, - {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, - {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, - {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, - {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, - {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"}, + {file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"}, + {file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"}, + {file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"}, + {file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"}, + {file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"}, + {file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"}, + {file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"}, + {file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"}, + {file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"}, + {file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"}, + {file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"}, + {file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"}, + {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] psutil = [ {file = "psutil-5.9.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b"}, @@ -1159,16 +1212,16 @@ pymongo = [ {file = "pymongo-3.12.3.tar.gz", hash = "sha256:0a89cadc0062a5e53664dde043f6c097172b8c1c5f0094490095282ff9995a5f"}, ] pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-gitlab = [ - {file = "python-gitlab-3.2.0.tar.gz", hash = "sha256:8f6ee81109fec231fc2b74e2c4035bb7de0548eaf82dd119fe294df2c4a524be"}, - {file = "python_gitlab-3.2.0-py3-none-any.whl", hash = "sha256:48f72e033c06ab1c244266af85de2cb0a175f8a3614417567e2b14254ead9b2e"}, + {file = "python-gitlab-3.3.0.tar.gz", hash = "sha256:fef25d41a62f91da82ee20f72a728b9c69eef34cf0a3005cdbb9a0b471d5b498"}, + {file = "python_gitlab-3.3.0-py3-none-any.whl", hash = "sha256:ab1fd4c98a206f22f01f832bc58f24a09952089b7bbf67cdaee6308e7797503f"}, ] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, @@ -1314,20 +1367,20 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tweepy = [ - {file = "tweepy-4.7.0-py2.py3-none-any.whl", hash = "sha256:d7e78c49232e849b660d82c7c2e504e8487d8014dcb73b39b490b61827965aba"}, - {file = "tweepy-4.7.0.tar.gz", hash = "sha256:82323505d549e3868e14a4570fc069ab3058ef95f9e578d1476d69bf2c831483"}, + {file = "tweepy-4.8.0-py2.py3-none-any.whl", hash = "sha256:f281bb53ab3ba999ff5e3d743d92d3ed543ee5551c7250948f9e56190ec7a43e"}, + {file = "tweepy-4.8.0.tar.gz", hash = "sha256:8ba5774ac1663b09e5fce1b030daf076f2c9b3ddbf2e7e7ea0bae762e3b1fe3e"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] tzdata = [ {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"}, ] tzlocal = [ - {file = "tzlocal-4.1-py3-none-any.whl", hash = "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"}, - {file = "tzlocal-4.1.tar.gz", hash = "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09"}, + {file = "tzlocal-4.2-py3-none-any.whl", hash = "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745"}, + {file = "tzlocal-4.2.tar.gz", hash = "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"}, ] ulid-py = [ {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, From 964c3747f10dd0d9d875b69fa88fffc2408e0b47 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 18:27:00 -0600 Subject: [PATCH 204/365] Fix console logging of commands --- jarvis/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/client.py b/jarvis/client.py index 3829b03..357cadc 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -85,6 +85,7 @@ class Jarvis(Snake): if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) + args += " " + " ".join(args) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") async def _sync_domains(self) -> None: From 9ef53178b10527169703e732bdf430a8f064af20 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 18:30:22 -0600 Subject: [PATCH 205/365] Change console arg context based on context type --- jarvis/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 357cadc..7c1cdb1 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -84,8 +84,9 @@ class Jarvis(Snake): name = ctx.invoked_name if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target - args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) - args += " " + " ".join(args) + args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) + elif isinstance(ctx, MessageContext): + args = " ".join(args) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") async def _sync_domains(self) -> None: From 07afd585771d042b26dbcd8e482a197d28d46f69 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 18:42:00 -0600 Subject: [PATCH 206/365] Update utility commands to use Discord timestamps --- jarvis/cogs/util.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 728a9d3..8e4c771 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -1,6 +1,5 @@ """JARVIS Utility Cog.""" import logging -import platform import re import secrets import string @@ -153,6 +152,7 @@ class UtilCog(Scale): 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), + EmbedField(name="Created At", value=f""), ] embed = build_embed( title="", @@ -190,18 +190,15 @@ class UtilCog(Scale): user_roles = user.roles if user_roles: user_roles = sorted(user.roles, key=lambda x: -x.position) - format_string = "%a, %b %-d, %Y %-I:%M %p" - if platform.system() == "Windows": - format_string = "%a, %b %#d, %Y %#I:%M %p" fields = [ EmbedField( name="Joined", - value=user.joined_at.strftime(format_string), + value=f"", ), EmbedField( name="Registered", - value=user.created_at.strftime(format_string), + value=f"", ), EmbedField( name=f"Roles [{len(user_roles)}]", @@ -267,6 +264,7 @@ class UtilCog(Scale): EmbedField(name="Threads", value=str(threads), inline=True), EmbedField(name="Members", value=str(members), inline=True), EmbedField(name="Roles", value=str(roles), inline=True), + EmbedField(name="Created At", value=f""), ] if len(role_list) < 1024: fields.append(EmbedField(name="Role List", value=role_list, inline=False)) From 5e5aedebf6c3c0d5ab038fb71406544171f26035 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:26:06 -0600 Subject: [PATCH 207/365] Make TwitterCog optional --- jarvis/cogs/twitter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index a043d16..0804075 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -245,4 +245,5 @@ class TwitterCog(Scale): def setup(bot: Snake) -> None: """Add TwitterCog to JARVIS""" - TwitterCog(bot) + if JarvisConfig.from_yaml().twitter: + TwitterCog(bot) From 5123b6f1985adb62e9cb8e790f4c43319f1ddf46 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:33:32 -0600 Subject: [PATCH 208/365] Add subreddit command --- jarvis/cogs/reddit.py | 147 ++++++++++++++++++++++++++++++++++++++++++ jarvis/config.py | 1 + poetry.lock | 142 +++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + 4 files changed, 283 insertions(+), 8 deletions(-) create mode 100644 jarvis/cogs/reddit.py diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py new file mode 100644 index 0000000..debd164 --- /dev/null +++ b/jarvis/cogs/reddit.py @@ -0,0 +1,147 @@ +"""JARVIS Reddit cog.""" +import asyncio +import logging + +from asyncpraw import Reddit +from asyncprawcore.exceptions import Forbidden, NotFound +from dis_snek import InteractionContext, Permissions, Scale, Snake +from dis_snek.client.utils.misc_utils import get +from dis_snek.models.discord.channel import ChannelTypes, GuildText +from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.snek.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) +from dis_snek.models.snek.command import check +from jarvis_core.db import q +from jarvis_core.db.models import Subreddit, SubredditFollow + +from jarvis import const +from jarvis.config import JarvisConfig +from jarvis.utils.permissions import admin_or_permissions + +DEFAULT_USER_AGENT = f"python:JARVIS-Tasks:{const.__version__} (by u/zevaryx)" + + +class RedditCog(Scale): + """JARVIS Reddit Cog.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + config = JarvisConfig.from_yaml() + config.reddit["user_agent"] = config.reddit.get("user_agent", DEFAULT_USER_AGENT) + self.api = Reddit(**config.reddit) + + reddit = SlashCommand(name="reddit", description="Manage Reddit follows") + + @reddit.subcommand(sub_cmd_name="follow", sub_cmd_description="Follow a Subreddit") + @slash_option( + name="name", + description="Subreddit display name", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="channel", + description="Channel to post to", + opt_type=OptionTypes.CHANNEL, + channel_types=[ChannelTypes.GUILD_TEXT], + required=True, + ) + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _reddit_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None: + name = name.replace("r/", "") + if len(name) > 20: + await ctx.send("Invalid Subreddit name", ephemeral=True) + return + + if not isinstance(channel, GuildText): + await ctx.send("Channel must be a text channel", ephemeral=True) + return + + try: + subreddit = await self.api.subreddit(name) + except (NotFound, Forbidden) as e: + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} on add") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + + sr = await Subreddit.find_one(q(display_name=subreddit.display_name)) + if not sr: + sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18) + await sr.commit() + + srf = SubredditFollow( + display_name=subreddit.display_name, + channel=channel.id, + guild=ctx.guild.id, + admin=ctx.author.id, + ) + await srf.commit() + + await ctx.send(f"Now following `r/{name}` in {channel.mention}") + + @reddit.subcommand(sub_cmd_name="unfollow", sub_cmd_description="Unfollow Subreddits") + @check(admin_or_permissions(Permissions.MANAGE_GUILD)) + async def _subreddit_unfollow(self, ctx: InteractionContext) -> None: + subs = SubredditFollow.find(q(guild=ctx.guild.id)) + subreddits = [] + async for sub in subs: + subreddits.append(sub) + if not subreddits: + await ctx.send("You need to follow a Subreddit first", ephemeral=True) + return + + options = [] + names = [] + for idx, subreddit in enumerate(subreddits): + sub = await Subreddit.find_one(q(display_name=subreddit.display_name)) + names.append(sub.display_name) + option = SelectOption(label=sub.display_name, value=str(idx)) + options.append(option) + + select = Select( + options=options, custom_id="to_delete", min_values=1, max_values=len(subreddits) + ) + + components = [ActionRow(select)] + block = "\n".join(x for x in names) + message = await ctx.send( + content=f"You are following the following subreddits:\n```\n{block}\n```\n\n" + "Please choose subreddits to unfollow", + components=components, + ) + + try: + context = await self.bot.wait_for_component( + check=lambda x: ctx.author.id == x.context.author.id, + messages=message, + timeout=60 * 5, + ) + for to_delete in context.context.values: + follow = get(subreddits, guild=ctx.guild.id, display_name=names[int(to_delete)]) + try: + await follow.delete() + except Exception: + self.logger.debug("Ignoring deletion error") + for row in components: + for component in row.components: + component.disabled = True + + block = "\n".join(names[int(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 + await message.edit(components=components) + + +def setup(bot: Snake) -> None: + """Add RedditCog to JARVIS""" + if JarvisConfig.from_yaml().reddit: + RedditCog(bot) diff --git a/jarvis/config.py b/jarvis/config.py index 2aec603..0c68d65 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -21,6 +21,7 @@ class JarvisConfig(CConfig): "gitlab_token": None, "max_messages": 1000, "twitter": None, + "reddit": None, } diff --git a/poetry.lock b/poetry.lock index 86c0f8b..aa9396a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,6 +12,14 @@ caio = ">=0.9.0,<0.10.0" [package.extras] develop = ["aiomisc", "asynctest", "pytest", "pytest-cov"] +[[package]] +name = "aiofiles" +version = "0.6.0" +description = "File support for asyncio." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "aiohttp" version = "3.8.1" @@ -43,6 +51,25 @@ python-versions = ">=3.6" [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "aiosqlite" +version = "0.17.0" +description = "asyncio bridge to the standard sqlite3 module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "async-generator" +version = "1.10" +description = "Async generators and context managers for Python 3.5+" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "async-timeout" version = "4.0.2" @@ -51,6 +78,61 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "asyncio-extras" +version = "1.3.2" +description = "Asynchronous generators, context managers and more for asyncio" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +async-generator = ">=1.3" + +[package.extras] +doc = ["sphinx-autodoc-typehints"] +test = ["pytest", "pytest-asyncio", "pytest-cov"] + +[[package]] +name = "asyncpraw" +version = "7.5.0" +description = "Async PRAW, an abbreviation for `Asynchronous Python Reddit API Wrapper`, is a python package that allows for simple access to reddit's API." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiofiles = "<=0.6.0" +aiosqlite = "<=0.17.0" +asyncio-extras = "<=1.3.2" +asyncprawcore = ">=2.1,<3" +update-checker = ">=0.18" + +[package.extras] +ci = ["coveralls"] +dev = ["packaging", "pre-commit", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio", "asynctest (>=0.13.0)", "mock (>=0.8)", "pytest (>=2.7.3)", "pytest-asyncio", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.1.1)"] +lint = ["pre-commit", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio"] +readthedocs = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-trio"] +test = ["asynctest (>=0.13.0)", "mock (>=0.8)", "pytest (>=2.7.3)", "pytest-asyncio", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.1.1)"] + +[[package]] +name = "asyncprawcore" +version = "2.3.0" +description = "Low-level asynchronous communication layer for Async PRAW 7+." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiohttp = "*" +yarl = "*" + +[package.extras] +ci = ["coveralls"] +dev = ["black", "flake8", "flynt", "pre-commit", "pydocstyle", "asynctest (>=0.13.0)", "mock (>=0.8)", "pytest", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.0.2)"] +lint = ["black", "flake8", "flynt", "pre-commit", "pydocstyle"] +test = ["asynctest (>=0.13.0)", "mock (>=0.8)", "pytest", "pytest-vcr", "testfixtures (>4.13.2,<7)", "vcrpy (==4.0.2)"] + [[package]] name = "attrs" version = "21.4.0" @@ -183,15 +265,15 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.7.0" -description = "" +version = "0.8.0" +description = "JARVIS core" category = "main" optional = false python-versions = "^3.10" develop = false [package.dependencies] -dis-snek = "*" +aiohttp = "^3.8.1" motor = "^2.5.1" orjson = "^3.6.6" pytz = "^2022.1" @@ -202,7 +284,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "15521e73fb84fb4282a484ff0c9bb88ed1d144ae" +resolved_reference = "02b60a29ebbab91d9ad4cf876eb10c2aea6f2231" [[package]] name = "marshmallow" @@ -333,11 +415,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -610,6 +692,22 @@ mongomock = ["mongomock"] motor = ["motor (>=2.0,<3.0)"] txmongo = ["txmongo (>=19.2.0)"] +[[package]] +name = "update-checker" +version = "0.18.0" +description = "A python module that will check for package updates." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.3.0" + +[package.extras] +dev = ["black", "flake8", "pytest (>=2.7.3)"] +lint = ["black", "flake8"] +test = ["pytest (>=2.7.3)"] + [[package]] name = "urllib3" version = "1.26.8" @@ -638,13 +736,17 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ed0f4a3c6383dfad0b1aa4c2a45e14283972734400b9b29095a04dfb7d14eb7b" +content-hash = "2397e36142e58e3a9a9215a6dae4e37c054fba46f168fb945ec26974bb9c2f21" [metadata.files] aiofile = [ {file = "aiofile-3.7.4-py3-none-any.whl", hash = "sha256:0e2a524e4714efda47ce8964b13d4da94cf553411f9f6da813df615a4cd73d95"}, {file = "aiofile-3.7.4.tar.gz", hash = "sha256:0aefa1d91d000d3a20a515d153db2ebf713076c7c94edf2fca85d3d83316abc5"}, ] +aiofiles = [ + {file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"}, + {file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"}, +] 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"}, @@ -723,10 +825,30 @@ aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] +aiosqlite = [ + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, +] +async-generator = [ + {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, + {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, +] 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"}, ] +asyncio-extras = [ + {file = "asyncio_extras-1.3.2-py3-none-any.whl", hash = "sha256:839568ba07c3470c9aa2c441aa2417c108f7d3755862bc2bd39d69b524303993"}, + {file = "asyncio_extras-1.3.2.tar.gz", hash = "sha256:084b62bebc19c6ba106d438a274bbb5566941c469128cd4af1a85f00a2c81f8d"}, +] +asyncpraw = [ + {file = "asyncpraw-7.5.0-py3-none-any.whl", hash = "sha256:b40f3db3464077a7a7e30a89181ba15ba4c5bc550dc2642e815b235f42ad8eb2"}, + {file = "asyncpraw-7.5.0.tar.gz", hash = "sha256:61aabf05052472d8b29e0f0500a6ec8b483129374d36dad286d94e4b6864572d"}, +] +asyncprawcore = [ + {file = "asyncprawcore-2.3.0-py3-none-any.whl", hash = "sha256:46c52e6cfe91801a8c9490a0ee29a85cbc6713ccc535d5c704d448aee9729e5b"}, + {file = "asyncprawcore-2.3.0.tar.gz", hash = "sha256:2a4a2d1ca7f78c8fa7d4903e6bd18cfe96742ad1f167b59473f64be0e7060d5d"}, +] attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, @@ -1390,6 +1512,10 @@ umongo = [ {file = "umongo-3.1.0-py2.py3-none-any.whl", hash = "sha256:f6913027651ae673d71aaf54285f9ebf1e49a3f57662e526d029ba72e1a3fcd5"}, {file = "umongo-3.1.0.tar.gz", hash = "sha256:20c72f09edae931285c22c1928862af35b90ec639a4dac2dbf015aaaac00e931"}, ] +update-checker = [ + {file = "update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd"}, + {file = "update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13"}, +] urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, diff --git a/pyproject.toml b/pyproject.toml index 247125e..ae18795 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ pastypy = "^1.0.1" dateparser = "^1.1.1" aiofile = "^3.7.4" molter = "^0.11.0" +asyncpraw = "^7.5.0" [build-system] requires = ["poetry-core>=1.0.0"] From 28d758ec5f752887ada6e9712439f4552533bb6e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:36:00 -0600 Subject: [PATCH 209/365] Fix build_embed imports --- jarvis/client.py | 2 +- jarvis/cogs/image.py | 4 +++- jarvis/utils/embeds.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 7c1cdb1..748d2c9 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -25,11 +25,11 @@ from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning from jarvis_core.filters import invites, url -from jarvis_core.util import build_embed from jarvis_core.util.ansi import RESET, Fore, Format, fmt from pastypy import AsyncPaste as Paste from jarvis import const +from jarvis.utils import build_embed from jarvis.utils.embeds import warning_embed DEFAULT_GUILD = 862402786116763668 diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 0dac7f8..048bb1b 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -15,7 +15,9 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) -from jarvis_core.util import build_embed, convert_bytesize, unconvert_bytesize +from jarvis_core.util import convert_bytesize, unconvert_bytesize + +from jarvis.utils import build_embed MIN_ACCURACY = 0.80 diff --git a/jarvis/utils/embeds.py b/jarvis/utils/embeds.py index 31c9fbc..c7a9126 100644 --- a/jarvis/utils/embeds.py +++ b/jarvis/utils/embeds.py @@ -1,7 +1,8 @@ """JARVIS bot-specific embeds.""" from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.user import Member -from jarvis_core.util import build_embed + +from jarvis.utils import build_embed def warning_embed(user: Member, reason: str) -> Embed: From 98e0c7979182b7c629598b6dca3dce0966ebf4b4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:37:32 -0600 Subject: [PATCH 210/365] Add Reddit to config --- jarvis/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/config.py b/jarvis/config.py index 0c68d65..6377cdc 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -49,6 +49,7 @@ class Config(object): gitlab_token: str = None, max_messages: int = 1000, twitter: dict = None, + reddit: dict = None, ) -> None: """Initialize the config object.""" self.token = token @@ -60,6 +61,7 @@ class Config(object): self.max_messages = max_messages self.gitlab_token = gitlab_token self.twitter = twitter + self.reddit = reddit self.sync = sync or os.environ.get("SYNC_COMMANDS", False) self.__db_loaded = False self.__mongo = MongoClient(**self.mongo["connect"]) From 86db405b1aad0a1e1924092ebd70a7f7413f41cc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:39:42 -0600 Subject: [PATCH 211/365] Do subreddit.load to load extra information --- jarvis/cogs/reddit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index debd164..4341abd 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -63,6 +63,7 @@ class RedditCog(Scale): try: subreddit = await self.api.subreddit(name) + await subreddit.load() except (NotFound, Forbidden) as e: self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} on add") await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) From 4601789da0473e6c88d33bd464bdd3454674c3ae Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:50:25 -0600 Subject: [PATCH 212/365] Enforce limits on subreddits --- jarvis/cogs/reddit.py | 12 ++++++++++++ jarvis/cogs/twitter.py | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 4341abd..ff6cf12 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -69,6 +69,18 @@ class RedditCog(Scale): await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return + count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))]) + if len(count) >= 12: + await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True) + return + + exists = await SubredditFollow.find_one( + q(display_name=subreddit.display_name, guild=ctx.guild.id) + ) + if exists: + await ctx.send("Subreddit already being followed in this guild", ephemeral=True) + return + sr = await Subreddit.find_one(q(display_name=subreddit.display_name)) if not sr: sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 0804075..326ee3a 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -80,16 +80,16 @@ class TwitterCog(Scale): ) return - count = len([i async for i in TwitterFollow.find(q(guild=ctx.guild.id))]) - if count >= 12: - await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) - return - exists = await TwitterFollow.find_one(q(twitter_id=account.id, guild=ctx.guild.id)) if exists: await ctx.send("Twitter account already being followed in this guild", ephemeral=True) return + count = len([i async for i in TwitterFollow.find(q(guild=ctx.guild.id))]) + if count >= 12: + await ctx.send("Cannot follow more than 12 Twitter accounts", ephemeral=True) + return + ta = await TwitterAccount.find_one(q(twitter_id=account.id)) if not ta: ta = TwitterAccount( From a79d7e1bf4971379d0a7d780472b3870a29e5ce0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 00:52:29 -0600 Subject: [PATCH 213/365] Change order of checks to make more sense --- jarvis/cogs/reddit.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index ff6cf12..32d3956 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -69,11 +69,6 @@ class RedditCog(Scale): await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return - count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))]) - if len(count) >= 12: - await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True) - return - exists = await SubredditFollow.find_one( q(display_name=subreddit.display_name, guild=ctx.guild.id) ) @@ -81,6 +76,11 @@ class RedditCog(Scale): await ctx.send("Subreddit already being followed in this guild", ephemeral=True) return + count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))]) + if len(count) >= 12: + await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True) + return + sr = await Subreddit.find_one(q(display_name=subreddit.display_name)) if not sr: sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18) From cac369535192e54618a2f08c6fbd7c616a4c5ae1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 01:26:19 -0600 Subject: [PATCH 214/365] Fix follow error --- jarvis/cogs/reddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 32d3956..679010b 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -77,7 +77,7 @@ class RedditCog(Scale): return count = len([i async for i in SubredditFollow.find(q(guild=ctx.guild.id))]) - if len(count) >= 12: + if count >= 12: await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True) return From 46a9f0342cec774f88f18698642ea598aba1d429 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 10:14:22 -0600 Subject: [PATCH 215/365] Update warnings --- jarvis/client.py | 13 ++++++++++++- jarvis/cogs/admin/warning.py | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index 748d2c9..aa4be21 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -2,7 +2,7 @@ import logging import re import traceback -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from aiohttp import ClientSession from dis_snek import Snake, listen @@ -312,10 +312,13 @@ class Jarvis(Snake): await message.delete() except Exception: self.logger.debug("Message deleted before action taken") + + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( active=True, admin=self.user.id, duration=24, + expires_at=expires_at, guild=message.guild.id, reason="Sent an invite link", user=message.author.id, @@ -342,10 +345,12 @@ class Jarvis(Snake): self.logger.debug( f"Massmention threshold on {message.guild.id}/{message.channel.id}/{message.id}" ) + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( active=True, admin=self.user.id, duration=24, + expires_at=expires_at, guild=message.guild.id, reason="Mass Mention", user=message.author.id, @@ -398,10 +403,12 @@ class Jarvis(Snake): self.logger.debug( f"Rolepinged role in {message.guild.id}/{message.channel.id}/{message.id}" ) + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( active=True, admin=self.user.id, duration=24, + expires_at=expires_at, guild=message.guild.id, reason="Pinged a blocked role/user with a blocked role", user=message.author.id, @@ -416,10 +423,12 @@ class Jarvis(Snake): self.logger.debug( f"Phishing url `{m}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( active=True, admin=self.user.id, duration=24, + expires_at=expires_at, guild=message.guild.id, reason="Phishing URL", user=message.author.id, @@ -445,10 +454,12 @@ class Jarvis(Snake): self.logger.debug( f"Scam url `{match.string}` detected in {message.guild.id}/{message.channel.id}/{message.id}" ) + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=24) await Warning( active=True, admin=self.user.id, duration=24, + expires_at=expires_at, guild=message.guild.id, reason="Unsafe URL", user=message.author.id, diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index 97e2f7b..c5ddbaf 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,5 +1,6 @@ """JARVIS WarningCog.""" import logging +from datetime import datetime, timedelta, timezone from dis_snek import InteractionContext, Permissions, Snake from dis_snek.client.utils.misc_utils import get_all @@ -59,12 +60,14 @@ class WarningCog(ModcaseCog): await ctx.send("User not in guild", ephemeral=True) return await ctx.defer() + expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=duration) await Warning( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, + expires_at=expires_at, active=True, ).commit() embed = warning_embed(user, reason) From e17f4daf0e300ef0ce7c7b0f859456740effbca3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 10:21:46 -0600 Subject: [PATCH 216/365] Add NSFW check to reddit follow --- jarvis/cogs/reddit.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 679010b..289701f 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -81,6 +81,13 @@ class RedditCog(Scale): await ctx.send("Cannot follow more than 12 Subreddits", ephemeral=True) return + if subreddit.over18 and not channel.nsfw: + await ctx.send( + "Subreddit is nsfw, but channel is not. Mark the channel NSFW first.", + ephemeral=True, + ) + return + sr = await Subreddit.find_one(q(display_name=subreddit.display_name)) if not sr: sr = Subreddit(display_name=subreddit.display_name, over18=subreddit.over18) From 3e3fb55e3583be2a74ea9149212be342ff6816eb Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 20 Apr 2022 10:22:11 -0600 Subject: [PATCH 217/365] Update poetry.lock --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index aa9396a..c939e7a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -265,7 +265,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.8.0" +version = "0.8.1" description = "JARVIS core" category = "main" optional = false @@ -284,7 +284,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "02b60a29ebbab91d9ad4cf876eb10c2aea6f2231" +resolved_reference = "257eaaea951d7f8ed5f6e1c4d01abed6d47e9f0d" [[package]] name = "marshmallow" @@ -415,11 +415,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -736,7 +736,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "2397e36142e58e3a9a9215a6dae4e37c054fba46f168fb945ec26974bb9c2f21" +content-hash = "181616e94a288cc8599183e8922431a23170ca91d0a1426307f969d3439b4e73" [metadata.files] aiofile = [ From 6f837d9ad37351355d9d17289363472b23836ae7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 13:06:10 -0600 Subject: [PATCH 218/365] Enforce name minimums on media feeds --- jarvis/cogs/reddit.py | 2 +- jarvis/cogs/twitter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 289701f..8838a9f 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -53,7 +53,7 @@ class RedditCog(Scale): @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _reddit_follow(self, ctx: InteractionContext, name: str, channel: GuildText) -> None: name = name.replace("r/", "") - if len(name) > 20: + if len(name) > 20 or len(name) < 3: await ctx.send("Invalid Subreddit name", ephemeral=True) return diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 326ee3a..733893d 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -63,7 +63,7 @@ class TwitterCog(Scale): self, ctx: InteractionContext, handle: str, channel: GuildText, retweets: bool = True ) -> None: handle = handle.lower() - if len(handle) > 15: + if len(handle) > 15 or len(handle) < 4: await ctx.send("Invalid Twitter handle", ephemeral=True) return From 339c103fcebb323ba7bd7f8c6f851e97d771b904 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 20 Apr 2022 13:33:07 -0600 Subject: [PATCH 219/365] Catch reddit redirect errors --- jarvis/cogs/reddit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 8838a9f..2b2f024 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -3,7 +3,7 @@ import asyncio import logging from asyncpraw import Reddit -from asyncprawcore.exceptions import Forbidden, NotFound +from asyncprawcore.exceptions import Forbidden, NotFound, Redirect from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import ChannelTypes, GuildText @@ -64,7 +64,7 @@ class RedditCog(Scale): try: subreddit = await self.api.subreddit(name) await subreddit.load() - except (NotFound, Forbidden) as e: + except (NotFound, Forbidden, Redirect) as e: self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} on add") await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return From fbb29c73d6250a4c30c342ca7e08eaac607543c2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 21 Apr 2022 23:48:05 -0600 Subject: [PATCH 220/365] Fix small bug in ctc2 guesses --- jarvis/cogs/ctc2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 90519dc..5d0a9de 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -112,7 +112,7 @@ class CTCCog(Scale): guesses = Guess.find().sort("correct", -1).sort("id", -1) fields = [] async for guess in guesses: - user = await ctx.guild.get_member(guess["user"]) + user = await ctx.guild.fetch_member(guess["user"]) if not user: user = await self.bot.fetch_user(guess["user"]) if not user: From 252adde96adaa26f469f05b5350f50120c31251f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 21 Apr 2022 23:49:07 -0600 Subject: [PATCH 221/365] User correct variables --- jarvis/cogs/ctc2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 5d0a9de..2e9e49c 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -118,7 +118,7 @@ class CTCCog(Scale): if not user: user = "[redacted]" if isinstance(user, (Member, User)): - user = user.name + "#" + user.discriminator + user = user.username + "#" + user.discriminator name = "Correctly" if guess["correct"] else "Incorrectly" name += " guessed by: " + user fields.append( From c83870e3389cfcb4086cf5c9f2de68dbc3708afb Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 21 Apr 2022 23:54:06 -0600 Subject: [PATCH 222/365] Only fetch user in ctc2, don't do member --- jarvis/cogs/ctc2.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 2e9e49c..9218f8d 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -112,11 +112,9 @@ class CTCCog(Scale): guesses = Guess.find().sort("correct", -1).sort("id", -1) fields = [] async for guess in guesses: - user = await ctx.guild.fetch_member(guess["user"]) + user = await self.bot.fetch_user(guess["user"]) if not user: - user = await self.bot.fetch_user(guess["user"]) - if not user: - user = "[redacted]" + user = "[redacted]" if isinstance(user, (Member, User)): user = user.username + "#" + user.discriminator name = "Correctly" if guess["correct"] else "Incorrectly" From 7b39a1e943ae9e3ae9420e6f6f35aef455963b92 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Thu, 21 Apr 2022 23:54:35 -0600 Subject: [PATCH 223/365] Update poetry.lock --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index c939e7a..1c6fd9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -284,7 +284,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "257eaaea951d7f8ed5f6e1c4d01abed6d47e9f0d" +resolved_reference = "ee002f6e7f59b059ab38b0d421812ea4b6860835" [[package]] name = "marshmallow" From 5d8dc31dc3d89f4640428f420e5dfbbcd1f27fe3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 21 Apr 2022 23:56:18 -0600 Subject: [PATCH 224/365] Update ctc2 guesses embed format --- jarvis/cogs/ctc2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index 9218f8d..f1de4c1 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -130,9 +130,9 @@ class CTCCog(Scale): for i in range(0, len(fields), 5): embed = build_embed( title="completethecodetwo.cards guesses", - description=f"{len(fields)} guesses so far", + description=f"**{len(fields)} guesses so far**", fields=fields[i : i + 5], - url="https://completethecodetwo.cards", + url="https://ctc2.zevaryx.com/gueses", ) embed.set_thumbnail(url="https://dev.zevaryx.com/db_logo.png") embed.set_footer( From 004d8d109166e0d69770260589fb3df8a28f7dda Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 22 Apr 2022 00:14:08 -0600 Subject: [PATCH 225/365] Add extra flag on reminder fetch --- jarvis/cogs/remindme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index 7225412..a3994b5 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -329,7 +329,7 @@ class RemindmeCog(Scale): embed.set_thumbnail(url=ctx.author.display_avatar) await ctx.send(embed=embed, ephemeral=reminder.private) - if reminder.remind_at <= datetime.now(tz=timezone.utc): + if reminder.remind_at <= datetime.now(tz=timezone.utc) and not reminder.active: try: await reminder.delete() except Exception: From d354211e0c9b441764a013e42cb5acd11ff8c7e9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 23 Apr 2022 14:40:57 -0600 Subject: [PATCH 226/365] Fix phishing sync --- jarvis/client.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index aa4be21..2cd3fe8 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -72,13 +72,21 @@ class Jarvis(Snake): self.logger.debug(f"Found {len(data)} changes to phishing domains") + add = 0 + sub = 0 + for update in data: if update["type"] == "add": - if update["domain"] not in self.phishing_domains: - self.phishing_domains.append(update["domain"]) + for domain in update["domains"]: + if domain not in self.phishing_domains: + add += 1 + self.phishing_domains.append(domain) elif update["type"] == "delete": - if update["domain"] in self.phishing_domains: - self.phishing_domains.remove(update["domain"]) + for domain in update["domains"]: + if domain in self.phishing_domains: + sub -= 1 + self.phishing_domains.remove(domain) + self.logger.debug(f"{add} additions, {sub} removals") async def _prerun(self, ctx: Context, *args, **kwargs) -> None: name = ctx.invoked_name From 2a280b66b65647562c668f4d8a26bf35c28705a0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Wed, 27 Apr 2022 08:21:46 -0600 Subject: [PATCH 227/365] Update emoji list --- jarvis/data/robotcamo.py | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/jarvis/data/robotcamo.py b/jarvis/data/robotcamo.py index 3c1647e..3577833 100644 --- a/jarvis/data/robotcamo.py +++ b/jarvis/data/robotcamo.py @@ -50,32 +50,32 @@ emotes = { } names = { - 852317928572715038: "rcA", - 852317954975727679: "rcB", - 852317972424818688: "rcC", - 852317990238421003: "rcD", - 852318044503539732: "rcE", - 852318058353786880: "rcF", - 852318073994477579: "rcG", - 852318105832259614: "rcH", - 852318122278125580: "rcI", - 852318145074167818: "rcJ", - 852318159952412732: "rcK", - 852318179358408704: "rcL", - 852318241555873832: "rcM", - 852318311115128882: "rcN", - 852318329951223848: "rcO", - 852318344643477535: "rcP", - 852318358920757248: "rcQ", - 852318385638211594: "rcR", - 852318401166311504: "rcS", - 852318421524938773: "rcT", - 852318435181854742: "rcU", - 852318453204647956: "rcV", - 852318470267731978: "rcW", - 852318484749877278: "rcX", - 852318504564555796: "rcY", - 852318519449092176: "rcZ", + 852317928572715038: "rcLetterA", + 852317954975727679: "rcLetterB", + 852317972424818688: "rcLetterC", + 852317990238421003: "rcLetterD", + 852318044503539732: "rcLetterE", + 852318058353786880: "rcLetterF", + 852318073994477579: "rcLetterG", + 852318105832259614: "rcLetterH", + 852318122278125580: "rcLetterI", + 852318145074167818: "rcLetterJ", + 852318159952412732: "rcLetterK", + 852318179358408704: "rcLetterL", + 852318241555873832: "rcLetterM", + 852318311115128882: "rcLetterN", + 852318329951223848: "rcLetterO", + 852318344643477535: "rcLetterP", + 852318358920757248: "rcLetterQ", + 852318385638211594: "rcLetterR", + 852318401166311504: "rcLetterS", + 852318421524938773: "rcLetterT", + 852318435181854742: "rcLetterU", + 852318453204647956: "rcLetterV", + 852318470267731978: "rcLetterW", + 852318484749877278: "rcLetterX", + 852318504564555796: "rcLetterY", + 852318519449092176: "rcLetterZ", 860663352740151316: "rc1", 860662785243348992: "rc2", 860662950011469854: "rc3", From d0a0e3a456a37f850aacb0ff1e7114ab51b7c9a8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 08:23:40 -0600 Subject: [PATCH 228/365] Fix error on mute if user has role higher than bot --- jarvis/cogs/admin/mute.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index a8df321..b659265 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta, timezone from dateparser import parse from dateparser_data.settings import default_parsers from dis_snek import InteractionContext, Permissions, Snake +from dis_snek.client.errors import Forbidden from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles from dis_snek.models.discord.user import Member @@ -125,8 +126,11 @@ class MuteCog(ModcaseCog): f"`{old_until}` is in the past, which isn't allowed", ephemeral=True ) return - embed = await self._apply_timeout(ctx, ctx.target, reason, until) - await response.send(embed=embed) + try: + embed = await self._apply_timeout(ctx, ctx.target, reason, until) + await response.send(embed=embed) + except Forbidden: + await response.send("Unable to mute this user", ephemeral=True) @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @@ -179,8 +183,11 @@ class MuteCog(ModcaseCog): return until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) - embed = await self._apply_timeout(ctx, user, reason, until) - await ctx.send(embed=embed) + try: + embed = await self._apply_timeout(ctx, user, reason, until) + await ctx.send(embed=embed) + except Forbidden: + await ctx.send("Unable to mute this user", ephemeral=True) @slash_command(name="unmute", description="Unmute a user") @slash_option( From ed172790f92ce13e1720e94ba4764ebbdd9b2111 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 14:33:49 -0600 Subject: [PATCH 229/365] Fix missed invites due to extra characters --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index 2cd3fe8..6dcc03e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -300,7 +300,7 @@ class Jarvis(Snake): # content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 # ) content = re.sub(r"\s+", "", message.content) - match = invites.search(content) + match = invites.search(content.replace("\\", "")) setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) if not setting: setting = Setting(guild=message.guild.id, setting="noinvite", value=True) From f04119a228f9843449f1f1727ad44fe9d315d47d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 15:05:20 -0600 Subject: [PATCH 230/365] Undo fix, rely on patch in jarvis_core --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index 6dcc03e..2cd3fe8 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -300,7 +300,7 @@ class Jarvis(Snake): # content="https://cdn.discordapp.com/attachments/664621130044407838/805218508866453554/tech.gif" # noqa: E501 # ) content = re.sub(r"\s+", "", message.content) - match = invites.search(content.replace("\\", "")) + match = invites.search(content) setting = await Setting.find_one(q(guild=message.guild.id, setting="noinvite")) if not setting: setting = Setting(guild=message.guild.id, setting="noinvite", value=True) From b8aa19175085cd07f9febfe70b42970ca2e8ffec Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 17:01:19 -0600 Subject: [PATCH 231/365] Add Rookout debugging --- jarvis/__init__.py | 6 ++- poetry.lock | 129 ++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + 3 files changed, 129 insertions(+), 7 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 009750e..2d90f15 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,6 +1,7 @@ """Main JARVIS package.""" import logging +import rook from dis_snek import Intents from jarvis_core.db import connect from jarvis_core.log import get_logger @@ -20,9 +21,12 @@ file_handler.setFormatter( ) logger.addHandler(file_handler) -intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGES +intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT restart_ctx = None +if jconfig.rook: + rook.start(token=jconfig.rook_token, labels={"env": "dev"}) + jarvis = Jarvis( intents=intents, sync_interactions=jconfig.sync, diff --git a/poetry.lock b/poetry.lock index 1c6fd9b..932d4ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -225,6 +225,14 @@ python-versions = ">=3.7" [package.dependencies] typing_extensions = ">=4,<5" +[[package]] +name = "distro" +version = "1.7.0" +description = "Distro - an OS platform information API" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "frozenlist" version = "1.3.0" @@ -233,6 +241,14 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "funcsigs" +version = "1.0.2" +description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "gitdb" version = "4.0.9" @@ -265,7 +281,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.8.1" +version = "0.8.2" description = "JARVIS core" category = "main" optional = false @@ -284,7 +300,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "ee002f6e7f59b059ab38b0d421812ea4b6860835" +resolved_reference = "0184d89d38660cd063c779b35f3e9ccc4ba86598" [[package]] name = "marshmallow" @@ -415,11 +431,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -438,6 +454,14 @@ python-versions = ">=3.7" docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +[[package]] +name = "protobuf" +version = "3.20.1" +description = "Protocol Buffers" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "psutil" version = "5.9.0" @@ -592,6 +616,26 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "rook" +version = "0.1.170" +description = "Rook is a Python package for on the fly debugging and data extraction for application in production" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +certifi = "*" +distro = "*" +funcsigs = "*" +protobuf = {version = ">=3.7.1,<=4.0.0", markers = "python_version > \"3.0\""} +psutil = ">=5.8.0" +six = ">=1.13" +websocket-client = ">=0.56,<0.58 || >0.58,<0.59 || >0.59,<1.0 || >1.0,<1.1 || >1.1" + +[package.extras] +ssl_backport = ["backports.ssl", "backports.ssl-match-hostname", "pyopenssl"] + [[package]] name = "six" version = "1.16.0" @@ -721,6 +765,19 @@ 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 = "websocket-client" +version = "1.3.2" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + [[package]] name = "yarl" version = "1.7.2" @@ -736,7 +793,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "181616e94a288cc8599183e8922431a23170ca91d0a1426307f969d3439b4e73" +content-hash = "8c6262c8ba1f4645117a59c789d092a6211c434b178744a9f637993914f4e48a" [metadata.files] aiofile = [ @@ -890,6 +947,10 @@ discord-typings = [ {file = "discord-typings-0.4.0.tar.gz", hash = "sha256:66bce666194e8f006914f788f940265c009cce9b63f9a8ce2bc7931d3d3ef11c"}, {file = "discord_typings-0.4.0-py3-none-any.whl", hash = "sha256:a390b614cbcbd82af083660c12e46536b4b790ac026f8d43bdd11c8953837ca0"}, ] +distro = [ + {file = "distro-1.7.0-py3-none-any.whl", hash = "sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b"}, + {file = "distro-1.7.0.tar.gz", hash = "sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39"}, +] 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"}, @@ -951,6 +1012,10 @@ frozenlist = [ {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, ] +funcsigs = [ + {file = "funcsigs-1.0.2-py2.py3-none-any.whl", hash = "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca"}, + {file = "funcsigs-1.0.2.tar.gz", hash = "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, @@ -1158,6 +1223,32 @@ pillow = [ {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"}, {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] +protobuf = [ + {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, + {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, + {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, + {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, + {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, + {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, + {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, + {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, + {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, + {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, + {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, + {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, + {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, + {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, + {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, + {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, +] 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"}, @@ -1476,6 +1567,28 @@ 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"}, ] +rook = [ + {file = "rook-0.1.170-cp27-cp27m-macosx_10_11_x86_64.whl", hash = "sha256:e2f90a44265606512ae434c44abe56b178dd5a5771016e3512b4f98598089576"}, + {file = "rook-0.1.170-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f4976bef70ec82f537668cb100d5510e9557e7edeba4aec46d9b354822e76cf1"}, + {file = "rook-0.1.170-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9228c7dc5cdd4235f258d33b5b55e1ddd2c1ac367f5326809698dc610242b88d"}, + {file = "rook-0.1.170-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:5977c819add5b648e5a0605337813d7add130a400d7eff41722c0c06ad27173e"}, + {file = "rook-0.1.170-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b0c81a76dfa98079e246ef54bdeb3d0bbbddef12c4a6b47a89e24386f6796589"}, + {file = "rook-0.1.170-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:ce97a8784a988f2dc9b9cc996574596587f374e1af77b7b6c8cc50d17f63c4d3"}, + {file = "rook-0.1.170-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa9a98871280168dbdfd3e1c4b6acf720932e7e449a8387314c49337008edbcc"}, + {file = "rook-0.1.170-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:a7ccdaeb4963786981770223117f918ea107f8d9e2b77324f7f9936afe0d1af5"}, + {file = "rook-0.1.170-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:152392d0243571620847e3267b11e7e50bea4f99e83ba94fef9ab0a828a418fb"}, + {file = "rook-0.1.170-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:048a0848c38de53dae6c06cb44c9a09b644716de6ecc28a3c96f462aefd976f8"}, + {file = "rook-0.1.170-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:58de02b2859458767963fc1641875a57003880d58f9eda0840ec96907541f70e"}, + {file = "rook-0.1.170-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:79f139e3397d5da35f951802a9fc800907df4c2fd82796a89c2841f6808aae14"}, + {file = "rook-0.1.170-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93628041f9f3b7a56af8eef8d42ca573095a988080d68f90637ee241e711d055"}, + {file = "rook-0.1.170-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:aabcff3f14bcb9674cb0fb23dd216dd64da532968142ae2bfc7c20a87be5540a"}, + {file = "rook-0.1.170-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:465151f1db95dea7004d47472c9a2ff751ba428cf634a2dd4846d0577979e837"}, + {file = "rook-0.1.170-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:259685e03ba8d2e60238ec3f45acffc45141989bb1c1de7f40ba791516e5d5d1"}, + {file = "rook-0.1.170-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:dfd180c89a1eee8525c10ffe658b20ff09cc9cd9a190889ffe735efe82d7c7e7"}, + {file = "rook-0.1.170-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dc123849965a11420324e89ccef26b015b7003f33cab5e5e6ce00450f7946fd"}, + {file = "rook-0.1.170-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3952e0ad674eaa26ab80db503b9890fc48f96e97a4ec64cd6a9cd3c04cd1e693"}, + {file = "rook-0.1.170.tar.gz", hash = "sha256:eeb9d98651822a47ccb9f9ec1a5cbce92d38970d24c8e5ce2f34908d73aa9f2a"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1520,6 +1633,10 @@ urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] +websocket-client = [ + {file = "websocket-client-1.3.2.tar.gz", hash = "sha256:50b21db0058f7a953d67cc0445be4b948d7fc196ecbeb8083d68d94628e4abf6"}, + {file = "websocket_client-1.3.2-py3-none-any.whl", hash = "sha256:722b171be00f2b90e1d4fb2f2b53146a536ca38db1da8ff49c972a4e1365d0ef"}, +] 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"}, diff --git a/pyproject.toml b/pyproject.toml index ae18795..0a8bd96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dateparser = "^1.1.1" aiofile = "^3.7.4" molter = "^0.11.0" asyncpraw = "^7.5.0" +rook = "^0.1.170" [build-system] requires = ["poetry-core>=1.0.0"] From fe8bd908545a9f97fcf746dfc87147f72616fd6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 17:02:26 -0600 Subject: [PATCH 232/365] Fix missing config for rook --- jarvis/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/config.py b/jarvis/config.py index 6377cdc..ba7d633 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -22,6 +22,7 @@ class JarvisConfig(CConfig): "max_messages": 1000, "twitter": None, "reddit": None, + "rook_token": None, } @@ -50,6 +51,7 @@ class Config(object): max_messages: int = 1000, twitter: dict = None, reddit: dict = None, + rook_token: str = None, ) -> None: """Initialize the config object.""" self.token = token @@ -63,6 +65,7 @@ class Config(object): self.twitter = twitter self.reddit = reddit self.sync = sync or os.environ.get("SYNC_COMMANDS", False) + self.rook_token = rook_token self.__db_loaded = False self.__mongo = MongoClient(**self.mongo["connect"]) From a68b9fd8f23d70e2f981af9b73b6aca693053239 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 17:03:17 -0600 Subject: [PATCH 233/365] Fix bad config name --- jarvis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 2d90f15..37aecf2 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -24,7 +24,7 @@ logger.addHandler(file_handler) intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT restart_ctx = None -if jconfig.rook: +if jconfig.rook_token: rook.start(token=jconfig.rook_token, labels={"env": "dev"}) jarvis = Jarvis( From 7af0b765f80df5421b43adf8f7c81c256f54bca6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Fri, 29 Apr 2022 19:16:45 -0600 Subject: [PATCH 234/365] Fix erroneous DMs for non-admin commands in admin cogs --- jarvis/utils/cogs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 7994290..7a342d9 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -21,6 +21,7 @@ from jarvis_core.db.models import ( from jarvis.utils import build_embed MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} +IGNORE_COMMANDS = {"Ban": ["bans"], "Kick": [], "Mute": ["unmute"], "Warning": ["warnings"]} class CacheCog(Scale): @@ -67,7 +68,7 @@ class ModcaseCog(Scale): """ name = self.__name__.replace("Cog", "") - if name not in ["Lock", "Lockdown", "Purge", "Roleping"]: + if name in MODLOG_LOOKUP and ctx.command not in IGNORE_COMMANDS[name]: user = kwargs.pop("user", None) if not user and not ctx.target_id: self.logger.warn(f"Admin action {name} missing user, exiting") From 966738b524373eac9d6ee703dc5351095eac7516 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 19:28:20 -0600 Subject: [PATCH 235/365] Add error handling in client --- jarvis/client.py | 12 +++++++++--- poetry.lock | 4 ++-- pyproject.toml | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 2cd3fe8..e93eae2 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -179,7 +179,10 @@ class Jarvis(Snake): f"\nException:\n```py\n{error_message}\n```" ) await ctx.send("Whoops! Encountered an error. The error has been logged.", ephemeral=True) - return await super().on_command_error(ctx, error, *args, **kwargs) + try: + return await super().on_command_error(ctx, error, *args, **kwargs) + except Exception as e: + self.logger.error("Uncaught exception", exc_info=e) # Modlog async def on_command(self, ctx: Context) -> None: @@ -368,8 +371,11 @@ class Jarvis(Snake): async def roleping(self, message: Message) -> None: """Handle roleping events.""" - if message.author.has_permission(Permissions.MANAGE_GUILD): - return + try: + if message.author.has_permission(Permissions.MANAGE_GUILD): + return + except Exception as e: + self.logger.error("Failed to get permissions, pretending check failed", exc_info=e) if await Roleping.collection.count_documents(q(guild=message.guild.id, active=True)) == 0: return rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) diff --git a/poetry.lock b/poetry.lock index 932d4ad..7aebbcb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -281,7 +281,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.8.2" +version = "0.8.4" description = "JARVIS core" category = "main" optional = false @@ -300,7 +300,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "0184d89d38660cd063c779b35f3e9ccc4ba86598" +resolved_reference = "94b1ecb478bdd61989cba850364d1242f6759982" [[package]] name = "marshmallow" diff --git a/pyproject.toml b/pyproject.toml index 0a8bd96..8840d88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ aiofile = "^3.7.4" molter = "^0.11.0" asyncpraw = "^7.5.0" rook = "^0.1.170" +rich = "^12.3.0" [build-system] requires = ["poetry-core>=1.0.0"] From 373e95b9af6e1a249dabed02d4919f2b5218cb10 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 19:41:29 -0600 Subject: [PATCH 236/365] Fix various setting and role-related bugs --- jarvis/cogs/rolegiver.py | 7 +++++++ jarvis/cogs/settings.py | 17 +++++++++++++---- jarvis/cogs/verify.py | 22 +++++++++++++++++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index d934de8..ec08eb8 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -41,6 +41,13 @@ class RolegiverCog(Scale): await ctx.send("Cannot add `@everyone` to rolegiver", ephemeral=True) return + if role.bot_managed or not role.is_assignable: + await ctx.send( + "Cannot assign this role, try lowering it below my role or using a different role", + ephemeral=True, + ) + return + setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) if setting and role.id in setting.roles: await ctx.send("Role already in rolegiver", ephemeral=True) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index d5e5086..39627fe 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -105,6 +105,12 @@ class SettingsCog(Scale): if role.id == ctx.guild.id: await ctx.send("Cannot set verified to `@everyone`", ephemeral=True) return + if role.bot_managed or not role.is_assignable: + await ctx.send( + "Cannot assign this role, try lowering it below my role or using a different role", + ephemeral=True, + ) + return await ctx.defer() await self.update_settings("verified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New verified role is `{role.name}`") @@ -118,6 +124,12 @@ class SettingsCog(Scale): if role.id == ctx.guild.id: await ctx.send("Cannot set unverified to `@everyone`", ephemeral=True) return + if role.bot_managed or not role.is_assignable: + await ctx.send( + "Cannot assign this role, try lowering it below my role or using a different role", + ephemeral=True, + ) + return await ctx.defer() await self.update_settings("unverified", role.id, ctx.guild.id) await ctx.send(f"Settings applied. New unverified role is `{role.name}`") @@ -169,14 +181,11 @@ class SettingsCog(Scale): await ctx.send("Setting `massmention` unset") @unset.subcommand(sub_cmd_name="verified", sub_cmd_description="Unset verified role") - @slash_option( - name="role", description="Verified role", opt_type=OptionTypes.ROLE, required=True - ) @check(admin_or_permissions(Permissions.MANAGE_GUILD)) async def _unset_verified(self, ctx: InteractionContext) -> None: await ctx.defer() await self.delete_settings("verified", ctx.guild.id) - await ctx.send("Setting `massmention` unset") + await ctx.send("Setting `verified` unset") @unset.subcommand(sub_cmd_name="unverified", sub_cmd_description="Unset unverified role") @check(admin_or_permissions(Permissions.MANAGE_GUILD)) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index 602f691..d7dda8e 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -45,7 +45,13 @@ class VerifyCog(Scale): if not role: message = await ctx.send("This guild has not enabled verification", ephemeral=True) return - if await ctx.guild.fetch_role(role.value) in ctx.author.roles: + verified_role = await ctx.guild.fetch_role(role.value) + if not verified_role: + await ctx.send("This guild has not enabled verification", ephemeral=True) + await role.delete() + return + + if verified_role in ctx.author.roles: await ctx.send("You are already verified.", ephemeral=True) return components = create_layout() @@ -69,12 +75,18 @@ class VerifyCog(Scale): for component in row.components: component.disabled = True setting = await Setting.find_one(q(guild=ctx.guild.id, setting="verified")) - role = await ctx.guild.fetch_role(setting.value) - await ctx.author.add_role(role, reason="Verification passed") + try: + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.add_role(role, reason="Verification passed") + except AttributeError: + self.logger.warn("Verified role deleted before verification finished") setting = await Setting.find_one(q(guild=ctx.guild.id, setting="unverified")) if setting: - role = await ctx.guild.fetch_role(setting.value) - await ctx.author.remove_role(role, reason="Verification passed") + try: + role = await ctx.guild.fetch_role(setting.value) + await ctx.author.remove_role(role, reason="Verification passed") + except AttributeError: + self.logger.warn("Unverified role deleted before verification finished") await response.context.edit_origin( content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", From a9067bbdeb6a809439216f6cea4ec0f2de8f4848 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 19:53:20 -0600 Subject: [PATCH 237/365] Catch all generic errors --- jarvis/client.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index e93eae2..e253b98 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -13,7 +13,7 @@ from dis_snek.api.events.discord import ( MessageDelete, MessageUpdate, ) -from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown +from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField @@ -107,7 +107,7 @@ class Jarvis(Snake): @listen() async def on_ready(self) -> None: - """Lepton on_ready override.""" + """NAFF on_ready override.""" await self._sync_domains() self._update_domains.start() self.logger.info("Logged in as {}".format(self.user)) # noqa: T001 @@ -118,10 +118,19 @@ class Jarvis(Snake): "{}&permissions=8&scope=bot%20applications.commands".format(self.user.id) ) + async def on_error(self, source: str, error: Exception, *args, **kwargs) -> None: + """NAFF on_error override.""" + if isinstance(error, HTTPException): + errors = error.search_for_message(error.errors) + out = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors) + self.logger.error(out, exc_info=error) + else: + self.logger.error(f"Ignoring exception in {source}", exc_info=error) + async def on_command_error( self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: - """Lepton on_command_error override.""" + """NAFF on_command_error override.""" self.logger.debug(f"Handling error in {ctx.invoked_name}: {error}") if isinstance(error, CommandOnCooldown): await ctx.send(str(error), ephemeral=True) @@ -186,7 +195,7 @@ class Jarvis(Snake): # Modlog async def on_command(self, ctx: Context) -> None: - """Lepton on_command override.""" + """NAFF on_command override.""" if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: From aaac9476860ae13a8fcc764b65f05dfe182e4b06 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 19:54:43 -0600 Subject: [PATCH 238/365] Add locals to error messages if debug is enabled --- jarvis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 37aecf2..8ade891 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -13,7 +13,7 @@ from jarvis.config import JarvisConfig __version__ = const.__version__ jconfig = JarvisConfig.from_yaml() -logger = get_logger("jarvis") +logger = get_logger("jarvis", show_locals=jconfig.log_level == "DEBUG") logger.setLevel(jconfig.log_level) file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") file_handler.setFormatter( From de02ab6f7ecbfdb5359bee909bc5d7c9929a6a9c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:04:19 -0600 Subject: [PATCH 239/365] Add jurigged, crash command --- jarvis/__init__.py | 4 + jarvis/cogs/botutil.py | 7 ++ poetry.lock | 184 ++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 4 files changed, 193 insertions(+), 3 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 8ade891..07ff256 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,6 +1,7 @@ """Main JARVIS package.""" import logging +import jurigged import rook from dis_snek import Intents from jarvis_core.db import connect @@ -21,6 +22,9 @@ file_handler.setFormatter( ) logger.addHandler(file_handler) +if jconfig.log_level == "DEBUG": + jurigged.watch(logger=logger) + intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT restart_ctx = None diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 2341c0e..1497dc2 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -48,6 +48,13 @@ class BotutilCog(Scale): log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) + @msg_command(name="crash") + async def _crash(self, ctx: MessageContext) -> None: + if ctx.author.id != self.bot.owner.id: + return + + raise Exception("As you wish") + def setup(bot: Snake) -> None: """Add BotutilCog to JARVIS""" diff --git a/poetry.lock b/poetry.lock index 7aebbcb..937bf8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -62,6 +62,14 @@ python-versions = ">=3.6" [package.dependencies] typing_extensions = ">=3.7.2" +[[package]] +name = "ansicon" +version = "1.89.0" +description = "Python wrapper for loading Jason Hood's ANSICON" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "async-generator" version = "1.10" @@ -147,6 +155,19 @@ 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 = "blessed" +version = "1.19.1" +description = "Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities." +category = "main" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +jinxed = {version = ">=1.1.0", markers = "platform_system == \"Windows\""} +six = ">=1.9.0" +wcwidth = ">=0.1.4" + [[package]] name = "caio" version = "0.9.5" @@ -177,6 +198,25 @@ python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] +[[package]] +name = "codefind" +version = "0.1.3" +description = "Find code objects and their referents" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + [[package]] name = "dateparser" version = "1.1.1" @@ -281,7 +321,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.8.4" +version = "0.8.5" description = "JARVIS core" category = "main" optional = false @@ -294,13 +334,42 @@ motor = "^2.5.1" orjson = "^3.6.6" pytz = "^2022.1" PyYAML = "^6.0" +rich = "^12.3.0" umongo = "^3.1.0" [package.source] type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "94b1ecb478bdd61989cba850364d1242f6759982" +resolved_reference = "457c7c060dc666d85d713259264bff4646dd813c" + +[[package]] +name = "jinxed" +version = "1.1.0" +description = "Jinxed Terminal Library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ansicon = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "jurigged" +version = "0.5.0" +description = "Live update of Python functions" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" + +[package.dependencies] +blessed = ">=1.17.12,<2.0.0" +codefind = ">=0.1.3,<0.2.0" +ovld = ">=0.3.1,<0.4.0" +watchdog = ">=1.0.2,<2.0.0" + +[package.extras] +develoop = ["giving (>=0.3.6,<0.4.0)", "rich (>=10.13.0,<11.0.0)", "hrepr (>=0.4.0,<0.5.0)"] [[package]] name = "marshmallow" @@ -408,6 +477,14 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "ovld" +version = "0.3.2" +description = "Overloading Python functions" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + [[package]] name = "packaging" version = "21.3" @@ -481,6 +558,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "pygments" +version = "2.12.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "pymongo" version = "3.12.3" @@ -616,6 +701,21 @@ python-versions = "*" [package.dependencies] requests = ">=2.0.1,<3.0.0" +[[package]] +name = "rich" +version = "12.3.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.6.3,<4.0.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + [[package]] name = "rook" version = "0.1.170" @@ -765,6 +865,25 @@ 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 = "watchdog" +version = "1.0.2" +description = "Filesystem events monitoring" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "websocket-client" version = "1.3.2" @@ -793,7 +912,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "8c6262c8ba1f4645117a59c789d092a6211c434b178744a9f637993914f4e48a" +content-hash = "f04633ce0eaf27dfdf39ae2cc8cbbf6116c9b4aab91b3e58f5ae6dbcd560eb50" [metadata.files] aiofile = [ @@ -886,6 +1005,10 @@ aiosqlite = [ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, ] +ansicon = [ + {file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"}, + {file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"}, +] async-generator = [ {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, @@ -910,6 +1033,10 @@ attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] +blessed = [ + {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, + {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, +] caio = [ {file = "caio-0.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:cd1c20aab04c18f0534b3f0b59103a94dede3c7d7b43c9cc525df3980b4c7c54"}, {file = "caio-0.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd316270757d77f384c97e336588267e7942c1f1492a3a2e07b9a80dca027538"}, @@ -935,6 +1062,14 @@ charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] +codefind = [ + {file = "codefind-0.1.3-py3-none-any.whl", hash = "sha256:3ffe85b74595b5c9f82391a11171ce7d68f1f555485720ab922f3b86f9bf30ec"}, + {file = "codefind-0.1.3.tar.gz", hash = "sha256:5667050361bf601a253031b2437d16b7d82cb0fa0e756d93e548c7b35ce6f910"}, +] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] dateparser = [ {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, @@ -1029,6 +1164,14 @@ idna = [ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] jarvis-core = [] +jinxed = [ + {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, + {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, +] +jurigged = [ + {file = "jurigged-0.5.0-py3-none-any.whl", hash = "sha256:28d86ca6d97669bc183773f7537e59f50fdd36e7637092fc2451b91bcc935d62"}, + {file = "jurigged-0.5.0.tar.gz", hash = "sha256:f23c3536b1654d2618d6e6b34f0752acf377c1b35283889d3a28663a7b1f72cb"}, +] marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, @@ -1175,6 +1318,10 @@ orjson = [ {file = "orjson-3.6.8-cp39-none-win_amd64.whl", hash = "sha256:0c89b419914d3d1f65a1b0883f377abe42a6e44f6624ba1c63e8846cbfc2fa60"}, {file = "orjson-3.6.8.tar.gz", hash = "sha256:e19d23741c5de13689bb316abfccea15a19c264e3ec8eb332a5319a583595ace"}, ] +ovld = [ + {file = "ovld-0.3.2-py3-none-any.whl", hash = "sha256:3a5f08f66573198b490fc69dcf93a2ad9b4d90fd1fef885cf7a8dbe565f17837"}, + {file = "ovld-0.3.2.tar.gz", hash = "sha256:f8918636c240a2935175406801944d4314823710b3afbd5a8db3e79cd9391c42"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1315,6 +1462,10 @@ pycryptodome = [ {file = "pycryptodome-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:7fb90a5000cc9c9ff34b4d99f7f039e9c3477700e309ff234eafca7b7471afc0"}, {file = "pycryptodome-3.14.1.tar.gz", hash = "sha256:e04e40a7f8c1669195536a37979dd87da2c32dbdc73d6fe35f0077b0c17c803b"}, ] +pygments = [ + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, +] 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"}, @@ -1567,6 +1718,10 @@ 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"}, ] +rich = [ + {file = "rich-12.3.0-py3-none-any.whl", hash = "sha256:0eb63013630c6ee1237e0e395d51cb23513de6b5531235e33889e8842bdf3a6f"}, + {file = "rich-12.3.0.tar.gz", hash = "sha256:7e8700cda776337036a712ff0495b04052fb5f957c7dfb8df997f88350044b64"}, +] rook = [ {file = "rook-0.1.170-cp27-cp27m-macosx_10_11_x86_64.whl", hash = "sha256:e2f90a44265606512ae434c44abe56b178dd5a5771016e3512b4f98598089576"}, {file = "rook-0.1.170-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f4976bef70ec82f537668cb100d5510e9557e7edeba4aec46d9b354822e76cf1"}, @@ -1633,6 +1788,29 @@ urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] +watchdog = [ + {file = "watchdog-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e2a531e71be7b5cc3499ae2d1494d51b6a26684bcc7c3146f63c810c00e8a3cc"}, + {file = "watchdog-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e7c73edef48f4ceeebb987317a67e0080e5c9228601ff67b3c4062fa020403c7"}, + {file = "watchdog-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e6574395aa6c1e14e0f030d9d7f35c2340a6cf95d5671354ce876ac3ffdd4d"}, + {file = "watchdog-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:27d9b4666938d5d40afdcdf2c751781e9ce36320788b70208d0f87f7401caf93"}, + {file = "watchdog-1.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f1ade0d0802503fda4340374d333408831cff23da66d7e711e279ba50fe6c4a"}, + {file = "watchdog-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f1d0e878fd69129d0d68b87cee5d9543f20d8018e82998efb79f7e412d42154a"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d948ad9ab9aba705f9836625b32e965b9ae607284811cd98334423f659ea537a"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:101532b8db506559e52a9b5d75a308729b3f68264d930670e6155c976d0e52a0"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:b1d723852ce90a14abf0ec0ca9e80689d9509ee4c9ee27163118d87b564a12ac"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:68744de2003a5ea2dfbb104f9a74192cf381334a9e2c0ed2bbe1581828d50b61"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:602dbd9498592eacc42e0632c19781c3df1728ef9cbab555fab6778effc29eeb"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:016b01495b9c55b5d4126ed8ae75d93ea0d99377084107c33162df52887cee18"}, + {file = "watchdog-1.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:5f1f3b65142175366ba94c64d8d4c8f4015825e0beaacee1c301823266b47b9b"}, + {file = "watchdog-1.0.2-py3-none-win32.whl", hash = "sha256:57f05e55aa603c3b053eed7e679f0a83873c540255b88d58c6223c7493833bac"}, + {file = "watchdog-1.0.2-py3-none-win_amd64.whl", hash = "sha256:f84146f7864339c8addf2c2b9903271df21d18d2c721e9a77f779493234a82b5"}, + {file = "watchdog-1.0.2-py3-none-win_ia64.whl", hash = "sha256:ee21aeebe6b3e51e4ba64564c94cee8dbe7438b9cb60f0bb350c4fa70d1b52c2"}, + {file = "watchdog-1.0.2.tar.gz", hash = "sha256:376cbc2a35c0392b0fe7ff16fbc1b303fd99d4dd9911ab5581ee9d69adc88982"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] websocket-client = [ {file = "websocket-client-1.3.2.tar.gz", hash = "sha256:50b21db0058f7a953d67cc0445be4b948d7fc196ecbeb8083d68d94628e4abf6"}, {file = "websocket_client-1.3.2-py3-none-any.whl", hash = "sha256:722b171be00f2b90e1d4fb2f2b53146a536ca38db1da8ff49c972a4e1365d0ef"}, diff --git a/pyproject.toml b/pyproject.toml index 8840d88..e0bca01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ molter = "^0.11.0" asyncpraw = "^7.5.0" rook = "^0.1.170" rich = "^12.3.0" +jurigged = "^0.5.0" [build-system] requires = ["poetry-core>=1.0.0"] From 4bf7e3785b35b9802fd8fdccfb32f3b5ff9eec63 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:05:48 -0600 Subject: [PATCH 240/365] Remove logger from jurigged watch --- jarvis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 07ff256..c73d1ae 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -23,7 +23,7 @@ file_handler.setFormatter( logger.addHandler(file_handler) if jconfig.log_level == "DEBUG": - jurigged.watch(logger=logger) + jurigged.watch() intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT restart_ctx = None From c854d2c2ba2b7678b407a2b3ec19f80c24d65328 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:12:43 -0600 Subject: [PATCH 241/365] Add scale-level check instead of per-command check --- jarvis/cogs/botutil.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 1497dc2..aeb6fc3 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -14,11 +14,14 @@ class BotutilCog(Scale): def __init__(self, bot: Snake): self.bot = bot self.logger = logging.getLogger(__name__) + self.add_scale_check(self.is_owner) + + async def is_owner(self, ctx: MessageContext) -> bool: + """Checks if author is bot owner.""" + return ctx.author.id == self.bot.owner.id @msg_command(name="tail") async def _tail(self, ctx: MessageContext, count: int = 10) -> None: - if ctx.author.id != self.bot.owner.id: - return lines = [] async with AIOFile("jarvis.log", "r") as af: async for line in LineReader(af): @@ -37,9 +40,6 @@ class BotutilCog(Scale): @msg_command(name="log") async def _log(self, ctx: MessageContext) -> None: - if ctx.author.id != self.bot.owner.id: - return - async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: raw = await af.read_bytes() @@ -50,9 +50,6 @@ class BotutilCog(Scale): @msg_command(name="crash") async def _crash(self, ctx: MessageContext) -> None: - if ctx.author.id != self.bot.owner.id: - return - raise Exception("As you wish") From 833147017a3697cbc16660fe3a335ebdd0e69faf Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:27:08 -0600 Subject: [PATCH 242/365] Add sysinfo command --- jarvis/cogs/botutil.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index aeb6fc3..3570153 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -1,12 +1,17 @@ """JARVIS bot utility commands.""" import logging +import platform from io import BytesIO +import psutil from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake +from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from molter import msg_command +from jarvis.utils import build_embed + class BotutilCog(Scale): """JARVIS Bot Utility Cog.""" @@ -52,6 +57,20 @@ class BotutilCog(Scale): async def _crash(self, ctx: MessageContext) -> None: raise Exception("As you wish") + @msg_command(name="sysinfo") + async def _sysinfo(self, ctx: MessageContext) -> None: + st_ts = int(self.bot.start_time.timestamp()) + ut_ts = int(psutil.boot_time()) + fields = [ + EmbedField(name="Operation System", value=platform.system() or "Unknown", inline=False), + EmbedField(name="Version", value=platform.release() or "N/A", inline=False), + EmbedField(name="System Start Time", value=f" ()"), + EmbedField(name="Python Version", value=platform.python_version()), + EmbedField(name="Bot Start Time", value=f" ()"), + ] + embed = build_embed(title="System Info", description="", fields=fields) + await ctx.send(embed=embed) + def setup(bot: Snake) -> None: """Add BotutilCog to JARVIS""" From 5fbd153245c4a549b9954f142ffa82009a2e77cd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:29:12 -0600 Subject: [PATCH 243/365] Add bot image to sysinfo command --- jarvis/cogs/botutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 3570153..8e808a0 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -69,6 +69,7 @@ class BotutilCog(Scale): EmbedField(name="Bot Start Time", value=f" ()"), ] embed = build_embed(title="System Info", description="", fields=fields) + embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) From 73cb15033928a08968e9312fd252fb8993f488a5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 20:51:16 -0600 Subject: [PATCH 244/365] Move rook, jurigged start to run --- jarvis/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index c73d1ae..e1ea96a 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -22,15 +22,9 @@ file_handler.setFormatter( ) logger.addHandler(file_handler) -if jconfig.log_level == "DEBUG": - jurigged.watch() - intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT restart_ctx = None -if jconfig.rook_token: - rook.start(token=jconfig.rook_token, labels={"env": "dev"}) - jarvis = Jarvis( intents=intents, sync_interactions=jconfig.sync, @@ -41,6 +35,10 @@ jarvis = Jarvis( async def run() -> None: """Run JARVIS""" + if jconfig.log_level == "DEBUG": + jurigged.watch() + if jconfig.rook_token: + rook.start(token=jconfig.rook_token, labels={"env": "dev"}) logger.info("Starting JARVIS") logger.debug("Connecting to database") connect(**jconfig.mongo["connect"], testing=jconfig.mongo["database"] != "jarvis") From dc47a446506788ed7a385f44fda2559ee7763e35 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:07:02 -0600 Subject: [PATCH 245/365] Add update command, get_all_commands helper --- jarvis/cogs/botutil.py | 38 +++++++++++++++++++++++++++++++++++++- jarvis/utils/__init__.py | 26 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 8e808a0..a02c1f8 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -1,16 +1,20 @@ """JARVIS bot utility commands.""" +import asyncio import logging import platform from io import BytesIO +from typing import get_type_hints +import git import psutil from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake +from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from molter import msg_command -from jarvis.utils import build_embed +from jarvis.utils import build_embed, get_all_commands class BotutilCog(Scale): @@ -72,6 +76,38 @@ class BotutilCog(Scale): embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) + @msg_command(name="update") + async def _update(self, ctx: MessageContext) -> None: + repo = git.Repo(".") + current_hash = repo.head.object.hexsha + origin = repo.remotes.origin + + if current_hash != origin.refs[repo.active_branch.name].object.hexsha: + current_commands = get_all_commands() + _ = origin.pull() + await asyncio.sleep(3) + new_commands = get_all_commands() + for module, commands in new_commands: + if module not in current_commands: + self.bot.load_extension(module) + elif len(current_commands[module]) != len(commands): + self.bot.reload_extension(module) + else: + for command in commands: + old_command = find( + lambda x: x.__name__ == command.__name__, current_commands + ) + old_args = get_type_hints(old_command) + new_args = get_type_hints(command) + if len(old_args) != len(new_args): + self.bot.reload_extension(module) + elif any(x not in old_args for x in new_args) or any( + x not in new_args for x in old_args + ): + self.bot.reload_extension(module) + elif any(new_args[x] != y for x, y in old_args): + self.bot.reload_extension(module) + def setup(bot: Snake) -> None: """Add BotutilCog to JARVIS""" diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 751bfe6..2937c8a 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,11 +1,18 @@ """JARVIS Utility Functions.""" +import importlib +import inspect from datetime import datetime, timezone from pkgutil import iter_modules +from types import ModuleType +from typing import Callable, Dict import git +from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.guild import AuditLogEntry from dis_snek.models.discord.user import Member +from dis_snek.models.snek import Scale +from dis_snek.models.snek.application_commands import SlashCommand import jarvis.cogs from jarvis.config import get_config @@ -71,6 +78,25 @@ def get_extensions(path: str = jarvis.cogs.__path__) -> list: return ["jarvis.cogs.{}".format(x) for x in vals] +def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: + commands = {} + for item in iter_modules(module.__path__): + new_module = importlib.import_module(f"{module.__name__}.{item.name}") + if item.ispkg: + if cmds := get_all_commands(new_module): + commands.update(cmds) + else: + inspect_result = inspect.getmembers(new_module) + cogs = [] + for _, val in inspect_result: + if inspect.isclass(val) and issubclass(val, Scale) and val is not Scale: + cogs.append(val) + for cog in cogs: + values = cog.__dict__.values() + commands[cog.__module__] = find_all(lambda x: isinstance(x, SlashCommand), values) + return {k: v for k, v in commands.items() if v} + + def update() -> int: """JARVIS update utility.""" repo = git.Repo(".") From e71df2ef10d11588fd3385fa2ca144545ef1fc39 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:09:25 -0600 Subject: [PATCH 246/365] Update update command --- jarvis/cogs/botutil.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index a02c1f8..4b0149b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -107,6 +107,9 @@ class BotutilCog(Scale): self.bot.reload_extension(module) elif any(new_args[x] != y for x, y in old_args): self.bot.reload_extension(module) + await ctx.reply("Updates applied!") + else: + await ctx.reply("No updates to apply") def setup(bot: Snake) -> None: From 960fbb1c50f52e7a83fc77be4590979561e46490 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:11:50 -0600 Subject: [PATCH 247/365] More detailed update response --- jarvis/cogs/botutil.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 4b0149b..38ddaf9 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -81,8 +81,10 @@ class BotutilCog(Scale): repo = git.Repo(".") current_hash = repo.head.object.hexsha origin = repo.remotes.origin + origin.fetch() + remote_hash = origin.refs[repo.active_branch.name].object.hexsha - if current_hash != origin.refs[repo.active_branch.name].object.hexsha: + if current_hash != remote_hash: current_commands = get_all_commands() _ = origin.pull() await asyncio.sleep(3) @@ -107,7 +109,7 @@ class BotutilCog(Scale): self.bot.reload_extension(module) elif any(new_args[x] != y for x, y in old_args): self.bot.reload_extension(module) - await ctx.reply("Updates applied!") + await ctx.reply(f"Updates applied! `{current_hash}` => `{remote_hash}`") else: await ctx.reply("No updates to apply") From 81571064848520f84ddf0c9d832020baf99976ed Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:13:01 -0600 Subject: [PATCH 248/365] Even more detailes response in update --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 38ddaf9..75afd58 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -111,7 +111,7 @@ class BotutilCog(Scale): self.bot.reload_extension(module) await ctx.reply(f"Updates applied! `{current_hash}` => `{remote_hash}`") else: - await ctx.reply("No updates to apply") + await ctx.reply(f"No updates to apply. `{current_hash}` == `{remote_hash}`") def setup(bot: Snake) -> None: From 849ea4770b559747ed593b7a17c5a01f5dc76891 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:14:02 -0600 Subject: [PATCH 249/365] Don't attempt to unpack origin pull --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 75afd58..a086ca3 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -86,7 +86,7 @@ class BotutilCog(Scale): if current_hash != remote_hash: current_commands = get_all_commands() - _ = origin.pull() + origin.pull() await asyncio.sleep(3) new_commands = get_all_commands() for module, commands in new_commands: From 6f0d157961e458ad20190e35f1cb588581d11b2e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:16:43 -0600 Subject: [PATCH 250/365] Add console logging to update --- jarvis/cogs/botutil.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index a086ca3..7ea30e1 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -85,8 +85,10 @@ class BotutilCog(Scale): remote_hash = origin.refs[repo.active_branch.name].object.hexsha if current_hash != remote_hash: + self.logger.info("Updating...") current_commands = get_all_commands() origin.pull() + self.logger.info("Changes pulled...") await asyncio.sleep(3) new_commands = get_all_commands() for module, commands in new_commands: @@ -109,6 +111,7 @@ class BotutilCog(Scale): self.bot.reload_extension(module) elif any(new_args[x] != y for x, y in old_args): self.bot.reload_extension(module) + self.logger.info("Updates applied") await ctx.reply(f"Updates applied! `{current_hash}` => `{remote_hash}`") else: await ctx.reply(f"No updates to apply. `{current_hash}` == `{remote_hash}`") From e4c3257f1b6358a97851ebf3ec21070f4886ba6b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:17:58 -0600 Subject: [PATCH 251/365] Properly iterate through new_commands --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 7ea30e1..3a3f4f2 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -91,7 +91,7 @@ class BotutilCog(Scale): self.logger.info("Changes pulled...") await asyncio.sleep(3) new_commands = get_all_commands() - for module, commands in new_commands: + for module, commands in new_commands.items(): if module not in current_commands: self.bot.load_extension(module) elif len(current_commands[module]) != len(commands): From 9499dc084564801b8d5478d53f64dd0934fa7400 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:20:19 -0600 Subject: [PATCH 252/365] Add debug statements to update --- jarvis/cogs/botutil.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 3a3f4f2..dcc0607 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -89,9 +89,12 @@ class BotutilCog(Scale): current_commands = get_all_commands() origin.pull() self.logger.info("Changes pulled...") + self.logger.debug("Sleeping for 3 seconds to allow changes") await asyncio.sleep(3) + self.logger.debug("Finished sleeping, loading new commands") new_commands = get_all_commands() for module, commands in new_commands.items(): + self.logger.debug(f"Processing {module}") if module not in current_commands: self.bot.load_extension(module) elif len(current_commands[module]) != len(commands): From 36cd2a6f0faf9122a38d79a8af44bba79b04f972 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:30:47 -0600 Subject: [PATCH 253/365] Fix error in old command finding --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index dcc0607..9899ad9 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -102,7 +102,7 @@ class BotutilCog(Scale): else: for command in commands: old_command = find( - lambda x: x.__name__ == command.__name__, current_commands + lambda x: x.__name__ == command.__name__, current_commands.values ) old_args = get_type_hints(old_command) new_args = get_type_hints(command) From 16d7ef940ef5fe0dbcf0fbba1fb2d03eb810a82e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sat, 30 Apr 2022 23:31:58 -0600 Subject: [PATCH 254/365] Properly fix error --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 9899ad9..3553999 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -102,7 +102,7 @@ class BotutilCog(Scale): else: for command in commands: old_command = find( - lambda x: x.__name__ == command.__name__, current_commands.values + lambda x: x.__name__ == command.__name__, current_commands[module] ) old_args = get_type_hints(old_command) new_args = get_type_hints(command) From 7110fb1db5b2cda78dfb7e1ad775d3ddc966e973 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:03:30 -0600 Subject: [PATCH 255/365] Add fancy rich table to update message --- jarvis/cogs/botutil.py | 63 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 3553999..fa955cf 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -13,6 +13,7 @@ from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from molter import msg_command +from rich.table import Table from jarvis.utils import build_embed, get_all_commands @@ -87,18 +88,23 @@ class BotutilCog(Scale): if current_hash != remote_hash: self.logger.info("Updating...") current_commands = get_all_commands() - origin.pull() + changes = origin.pull() + self.logger.info("Changes pulled...") self.logger.debug("Sleeping for 3 seconds to allow changes") await asyncio.sleep(3) self.logger.debug("Finished sleeping, loading new commands") + + reloaded = [] new_commands = get_all_commands() for module, commands in new_commands.items(): self.logger.debug(f"Processing {module}") if module not in current_commands: self.bot.load_extension(module) + reloaded.append(module) elif len(current_commands[module]) != len(commands): self.bot.reload_extension(module) + reloaded.append(module) else: for command in commands: old_command = find( @@ -108,16 +114,67 @@ class BotutilCog(Scale): new_args = get_type_hints(command) if len(old_args) != len(new_args): self.bot.reload_extension(module) + reloaded.append(module) elif any(x not in old_args for x in new_args) or any( x not in new_args for x in old_args ): self.bot.reload_extension(module) + reloaded.append(module) elif any(new_args[x] != y for x, y in old_args): self.bot.reload_extension(module) + reloaded.append(module) + + file_changes = {} + for change in changes: + if change.commit.hexsha == current_hash: + break + files = change.commit.stats.files + for file, stats in files.items(): + if file not in file_changes: + file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} + for k, v in stats.items(): + file_changes[file][k] += v + + table = Table(title="File Changes") + + table.add_column("File", justify="left", style="white", no_wrap=True) + table.add_column("Insertions", justify="center", style="green") + table.add_column("Deletions", justify="center", style="red") + table.add_column("Lines", justify="center", style="magenta") + + i_total = 0 + d_total = 0 + l_total = 0 + for file, stats in file_changes.items(): + i_total += stats["insertions"] + d_total += stats["deletions"] + l_total += stats["lines"] + table.add_row( + file, + str(stats["insertions"]), + str(stats["deletions"]), + str(stats["lines"]), + ) + + table.add_row("Total", str(i_total), str(d_total), str(l_total)) + + fields = [ + EmbedField(name="Old Commit", value=current_hash), + EmbedField(name="New Commit", value=remote_hash), + EmbedField(name="Files Changes", value=f"```ansi\n{table}\n```"), + ] + + embed = build_embed( + "Update Status", description="Updates have been applied", fields=fields + ) + self.logger.info("Updates applied") - await ctx.reply(f"Updates applied! `{current_hash}` => `{remote_hash}`") + await ctx.reply(embed=embed) + else: - await ctx.reply(f"No updates to apply. `{current_hash}` == `{remote_hash}`") + embed = build_embed(title="Update Status", description="No changes applied", fields=[]) + embed.set_footer(text=current_hash) + await ctx.reply(embed=embed) def setup(bot: Snake) -> None: From 611051e6a117927b2ad285386343ff8237a429a6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:07:10 -0600 Subject: [PATCH 256/365] Match against resolved name instead of non-existent __name__ --- jarvis/cogs/botutil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index fa955cf..61b0839 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -108,7 +108,8 @@ class BotutilCog(Scale): else: for command in commands: old_command = find( - lambda x: x.__name__ == command.__name__, current_commands[module] + lambda x: x.resolved_name == command.resolved_name, + current_commands[module], ) old_args = get_type_hints(old_command) new_args = get_type_hints(command) From c260b935303f36eee215c57dd60e1117f58ad833 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:09:50 -0600 Subject: [PATCH 257/365] Add ping command (internal only) --- jarvis/cogs/botutil.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 61b0839..36c8b3d 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -77,6 +77,10 @@ class BotutilCog(Scale): embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) + @msg_command(name="ping") + async def _ping(self, ctx: MessageContext) -> None: + await ctx.reply(f"{self.bot.average_latency}ms") + @msg_command(name="update") async def _update(self, ctx: MessageContext) -> None: repo = git.Repo(".") From 334b578ae6254c070f26d7730b572b3679b5a83f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:19:42 -0600 Subject: [PATCH 258/365] Utilize SlashCommand fields for validation --- jarvis/cogs/botutil.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 36c8b3d..f0fd2c1 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -3,7 +3,6 @@ import asyncio import logging import platform from io import BytesIO -from typing import get_type_hints import git import psutil @@ -115,17 +114,23 @@ class BotutilCog(Scale): lambda x: x.resolved_name == command.resolved_name, current_commands[module], ) - old_args = get_type_hints(old_command) - new_args = get_type_hints(command) + + # Extract useful info + old_args = old_command.options + old_arg_names = [x.name for x in old_args] + new_args = command.options + new_arg_names = [x.name for x in new_args] + + # Check if number arguments have changed if len(old_args) != len(new_args): self.bot.reload_extension(module) reloaded.append(module) - elif any(x not in old_args for x in new_args) or any( - x not in new_args for x in old_args + elif any(x not in old_arg_names for x in new_arg_names) or any( + x not in new_arg_names for x in old_arg_names ): self.bot.reload_extension(module) reloaded.append(module) - elif any(new_args[x] != y for x, y in old_args): + elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): self.bot.reload_extension(module) reloaded.append(module) From 223bbd8628928ce74c050d5617dd8b4b9efd3809 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:27:37 -0600 Subject: [PATCH 259/365] Remove ping command --- jarvis/cogs/botutil.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index f0fd2c1..3959e40 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -76,10 +76,6 @@ class BotutilCog(Scale): embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) - @msg_command(name="ping") - async def _ping(self, ctx: MessageContext) -> None: - await ctx.reply(f"{self.bot.average_latency}ms") - @msg_command(name="update") async def _update(self, ctx: MessageContext) -> None: repo = git.Repo(".") From 6c8b6dd2c03f68f9953ff7430491ac20aa7c304e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:30:11 -0600 Subject: [PATCH 260/365] Better check if options exist --- jarvis/cogs/botutil.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 3959e40..ea3fdb2 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -113,9 +113,15 @@ class BotutilCog(Scale): # Extract useful info old_args = old_command.options - old_arg_names = [x.name for x in old_args] + if old_args: + old_arg_names = [x.name for x in old_args] new_args = command.options - new_arg_names = [x.name for x in new_args] + if new_args: + new_arg_names = [x.name for x in new_args] + + # No changes + if not old_args and not new_args: + continue # Check if number arguments have changed if len(old_args) != len(new_args): From 573e697ab4de8b02928018d4f255fc9b5bc824d9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 09:54:36 -0600 Subject: [PATCH 261/365] More stats in update --- jarvis/cogs/botutil.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index ea3fdb2..a0b60e9 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -95,12 +95,18 @@ class BotutilCog(Scale): self.logger.debug("Finished sleeping, loading new commands") reloaded = [] + loaded = [] + unloaded = [] new_commands = get_all_commands() + for module in current_commands: + if module not in new_commands: + self.bot.unload_extension(module) + unloaded.append(module) for module, commands in new_commands.items(): self.logger.debug(f"Processing {module}") if module not in current_commands: self.bot.load_extension(module) - reloaded.append(module) + loaded.append(module) elif len(current_commands[module]) != len(commands): self.bot.reload_extension(module) reloaded.append(module) @@ -169,11 +175,16 @@ class BotutilCog(Scale): ) table.add_row("Total", str(i_total), str(d_total), str(l_total)) + new = "\n".join(loaded) + removed = "\n".join(unloaded) + changed = "\n".join(reloaded) fields = [ EmbedField(name="Old Commit", value=current_hash), EmbedField(name="New Commit", value=remote_hash), - EmbedField(name="Files Changes", value=f"```ansi\n{table}\n```"), + EmbedField(name="New Modules", value=f"```\n{new}\n```"), + EmbedField(name="Removed Modules", value=f"```\n{removed}\n```"), + EmbedField(name="Changed Modules", value=f"```\n{changed}\n```"), ] embed = build_embed( @@ -181,7 +192,7 @@ class BotutilCog(Scale): ) self.logger.info("Updates applied") - await ctx.reply(embed=embed) + await ctx.reply(f"File Changes: ```ansi\n{table}\n```", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) From a6a7d5ec18df3ef412cbc8d98d12bd76a27db21c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:02:04 -0600 Subject: [PATCH 262/365] Utilize console.capture to get raw table text --- jarvis/cogs/botutil.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index a0b60e9..5e9d8e9 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -12,6 +12,7 @@ from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from molter import msg_command +from rich.console import Console from rich.table import Table from jarvis.utils import build_embed, get_all_commands @@ -175,6 +176,9 @@ class BotutilCog(Scale): ) table.add_row("Total", str(i_total), str(d_total), str(l_total)) + console = Console() + with console.capture() as capture: + console.print(table) new = "\n".join(loaded) removed = "\n".join(unloaded) changed = "\n".join(reloaded) @@ -192,7 +196,7 @@ class BotutilCog(Scale): ) self.logger.info("Updates applied") - await ctx.reply(f"File Changes: ```ansi\n{table}\n```", embed=embed) + await ctx.reply(f"File Changes: ```ansi\n{capture}\n```", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) From 62370324faad2995a1779eadf7d5e7426d368a2f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:04:03 -0600 Subject: [PATCH 263/365] Only show relevant changes in embed --- jarvis/cogs/botutil.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 5e9d8e9..c01b126 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -186,10 +186,13 @@ class BotutilCog(Scale): fields = [ EmbedField(name="Old Commit", value=current_hash), EmbedField(name="New Commit", value=remote_hash), - EmbedField(name="New Modules", value=f"```\n{new}\n```"), - EmbedField(name="Removed Modules", value=f"```\n{removed}\n```"), - EmbedField(name="Changed Modules", value=f"```\n{changed}\n```"), ] + if loaded: + fields.append(EmbedField(name="New Modules", value=f"```\n{new}\n```")) + if removed: + fields.append(EmbedField(name="Removed Modules", value=f"```\n{removed}\n```")) + if changed: + fields.append(EmbedField(name="Changed Modules", value=f"```\n{changed}\n```")) embed = build_embed( "Update Status", description="Updates have been applied", fields=fields From 89cd78885431cbf48d85a22a6d648c3846693a46 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:04:54 -0600 Subject: [PATCH 264/365] Call capture.get in file change output --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index c01b126..b1c30ea 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -199,7 +199,7 @@ class BotutilCog(Scale): ) self.logger.info("Updates applied") - await ctx.reply(f"File Changes: ```ansi\n{capture}\n```", embed=embed) + await ctx.reply(f"File Changes: ```ansi\n{capture.get()}\n```", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) From a7091b9ef66a1bbe05352ef6513f229ada061376 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:06:47 -0600 Subject: [PATCH 265/365] Add hash to embed footer on updates --- jarvis/cogs/botutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index b1c30ea..5164f1b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -197,6 +197,7 @@ class BotutilCog(Scale): embed = build_embed( "Update Status", description="Updates have been applied", fields=fields ) + embed.set_footer(text=remote_hash) self.logger.info("Updates applied") await ctx.reply(f"File Changes: ```ansi\n{capture.get()}\n```", embed=embed) From b862411fd2cf9118eb4b595caeefb59feda6cc44 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:13:07 -0600 Subject: [PATCH 266/365] Fix output on update --- jarvis/cogs/botutil.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 5164f1b..2228c9c 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -197,10 +197,9 @@ class BotutilCog(Scale): embed = build_embed( "Update Status", description="Updates have been applied", fields=fields ) - embed.set_footer(text=remote_hash) self.logger.info("Updates applied") - await ctx.reply(f"File Changes: ```ansi\n{capture.get()}\n```", embed=embed) + await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) From 78776e7ae586dc4188b313b10e8ed3106642e25b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:15:53 -0600 Subject: [PATCH 267/365] Log table to console --- jarvis/cogs/botutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 2228c9c..ea63cb9 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -176,6 +176,7 @@ class BotutilCog(Scale): ) table.add_row("Total", str(i_total), str(d_total), str(l_total)) + self.logger.debug(table) console = Console() with console.capture() as capture: console.print(table) From 0660a3aec9a02e28aa067c1d33832927a8961828 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:19:43 -0600 Subject: [PATCH 268/365] Add git logo as image to embeds --- jarvis/cogs/botutil.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index ea63cb9..bec04c7 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -198,6 +198,7 @@ class BotutilCog(Scale): embed = build_embed( "Update Status", description="Updates have been applied", fields=fields ) + embed.set_image(url="https://dev.zevaryx.com/git.png") self.logger.info("Updates applied") await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) @@ -205,6 +206,7 @@ class BotutilCog(Scale): else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) embed.set_footer(text=current_hash) + embed.set_image(url="https://dev.zevaryx.com/git.png") await ctx.reply(embed=embed) From 89f22db78ddc355b9ae4fbc48d7baf988bf46779 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:21:55 -0600 Subject: [PATCH 269/365] Add fallback in case table is too big --- jarvis/cogs/botutil.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index bec04c7..806ce0b 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -8,6 +8,7 @@ import git import psutil from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake +from dis_snek.client.errors import BadRequest from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File @@ -176,10 +177,11 @@ class BotutilCog(Scale): ) table.add_row("Total", str(i_total), str(d_total), str(l_total)) - self.logger.debug(table) console = Console() with console.capture() as capture: console.print(table) + self.logger.debug(capture.get()) + self.logger.debug(len(capture.get())) new = "\n".join(loaded) removed = "\n".join(unloaded) changed = "\n".join(reloaded) @@ -201,7 +203,10 @@ class BotutilCog(Scale): embed.set_image(url="https://dev.zevaryx.com/git.png") self.logger.info("Updates applied") - await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) + try: + await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) + except BadRequest: + await ctx.reply(f"Total Changes: {l_total}", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) From b685823b7930594cd7f387d234583aefdbf29496 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:30:00 -0600 Subject: [PATCH 270/365] Add uptime to status --- jarvis/cogs/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 8e4c771..702e3f9 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -54,10 +54,12 @@ class UtilCog(Scale): desc = f"All systems online\nConnected to **{len(self.bot.guilds)}** guilds" color = "#3498db" fields = [] + uptime = int(self.bot.start_time.timestamp()) fields.append(EmbedField(name="dis-snek", value=const.__version__)) fields.append(EmbedField(name="Version", value=jconst.__version__, inline=False)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) + fields.append(EmbedField(name="Online Since", value=f"", inline=False)) num_domains = len(self.bot.phishing_domains) fields.append( EmbedField( From 0e492938e70706e06003d5c388260e073113ca98 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:33:39 -0600 Subject: [PATCH 271/365] Catch HTTPExceptions instead of BadRequest --- jarvis/cogs/botutil.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 806ce0b..a95a89e 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -8,7 +8,7 @@ import git import psutil from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake -from dis_snek.client.errors import BadRequest +from dis_snek.client.errors import HTTPException from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File @@ -145,7 +145,7 @@ class BotutilCog(Scale): reloaded.append(module) file_changes = {} - for change in changes: + for change in sorted(changes, key=lambda x: x.commit.committed_datetime, reverse=True): if change.commit.hexsha == current_hash: break files = change.commit.stats.files @@ -205,7 +205,7 @@ class BotutilCog(Scale): self.logger.info("Updates applied") try: await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) - except BadRequest: + except HTTPException: await ctx.reply(f"Total Changes: {l_total}", embed=embed) else: From 0b67131ded26a3d3b4a013b735a6a2ebec4ba84b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:37:43 -0600 Subject: [PATCH 272/365] Update client.on_command_error to provide more useful feedback --- jarvis/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index e253b98..7d192f4 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -169,6 +169,10 @@ class Jarvis(Snake): callback_args=callback_args, callback_kwargs=callback_kwargs, ) + tb = traceback.format_exception(error) + if isinstance(error, HTTPException): + errors = error.search_for_message(error.errors) + tb[-1] = f"HTTPException: {error.status}|{error.response.reason}: " + "\n".join(errors) error_message = "".join(traceback.format_exception(error)) if len(full_message + error_message) >= 1800: error_message = "\n ".join(error_message.split("\n")) From 92cd0b1eaeb08fc382118efcf1be84f157486571 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 10:39:31 -0600 Subject: [PATCH 273/365] Set git image to thumbnail, not image --- jarvis/cogs/botutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index a95a89e..3b901d4 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -200,7 +200,7 @@ class BotutilCog(Scale): embed = build_embed( "Update Status", description="Updates have been applied", fields=fields ) - embed.set_image(url="https://dev.zevaryx.com/git.png") + embed.set_thumbnail(url="https://dev.zevaryx.com/git.png") self.logger.info("Updates applied") try: @@ -211,7 +211,7 @@ class BotutilCog(Scale): else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) embed.set_footer(text=current_hash) - embed.set_image(url="https://dev.zevaryx.com/git.png") + embed.set_thumbnail(url="https://dev.zevaryx.com/git.png") await ctx.reply(embed=embed) From b1d5415625119820c760eaa655910b42c3ce9ce8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:01:46 -0600 Subject: [PATCH 274/365] Create helper function for updates for easier debugging --- jarvis/cogs/botutil.py | 125 +++------------------------ jarvis/utils/updates.py | 187 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 112 deletions(-) create mode 100644 jarvis/utils/updates.py diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 3b901d4..5b60d2c 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -1,22 +1,19 @@ """JARVIS bot utility commands.""" -import asyncio import logging import platform from io import BytesIO -import git import psutil from aiofile import AIOFile, LineReader from dis_snek import MessageContext, Scale, Snake from dis_snek.client.errors import HTTPException -from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from molter import msg_command from rich.console import Console -from rich.table import Table -from jarvis.utils import build_embed, get_all_commands +from jarvis.utils import build_embed +from jarvis.utils.updates import update class BotutilCog(Scale): @@ -80,118 +77,23 @@ class BotutilCog(Scale): @msg_command(name="update") async def _update(self, ctx: MessageContext) -> None: - repo = git.Repo(".") - current_hash = repo.head.object.hexsha - origin = repo.remotes.origin - origin.fetch() - remote_hash = origin.refs[repo.active_branch.name].object.hexsha - - if current_hash != remote_hash: - self.logger.info("Updating...") - current_commands = get_all_commands() - changes = origin.pull() - - self.logger.info("Changes pulled...") - self.logger.debug("Sleeping for 3 seconds to allow changes") - await asyncio.sleep(3) - self.logger.debug("Finished sleeping, loading new commands") - - reloaded = [] - loaded = [] - unloaded = [] - new_commands = get_all_commands() - for module in current_commands: - if module not in new_commands: - self.bot.unload_extension(module) - unloaded.append(module) - for module, commands in new_commands.items(): - self.logger.debug(f"Processing {module}") - if module not in current_commands: - self.bot.load_extension(module) - loaded.append(module) - elif len(current_commands[module]) != len(commands): - self.bot.reload_extension(module) - reloaded.append(module) - else: - for command in commands: - old_command = find( - lambda x: x.resolved_name == command.resolved_name, - current_commands[module], - ) - - # Extract useful info - old_args = old_command.options - if old_args: - old_arg_names = [x.name for x in old_args] - new_args = command.options - if new_args: - new_arg_names = [x.name for x in new_args] - - # No changes - if not old_args and not new_args: - continue - - # Check if number arguments have changed - if len(old_args) != len(new_args): - self.bot.reload_extension(module) - reloaded.append(module) - elif any(x not in old_arg_names for x in new_arg_names) or any( - x not in new_arg_names for x in old_arg_names - ): - self.bot.reload_extension(module) - reloaded.append(module) - elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - self.bot.reload_extension(module) - reloaded.append(module) - - file_changes = {} - for change in sorted(changes, key=lambda x: x.commit.committed_datetime, reverse=True): - if change.commit.hexsha == current_hash: - break - files = change.commit.stats.files - for file, stats in files.items(): - if file not in file_changes: - file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} - for k, v in stats.items(): - file_changes[file][k] += v - - table = Table(title="File Changes") - - table.add_column("File", justify="left", style="white", no_wrap=True) - table.add_column("Insertions", justify="center", style="green") - table.add_column("Deletions", justify="center", style="red") - table.add_column("Lines", justify="center", style="magenta") - - i_total = 0 - d_total = 0 - l_total = 0 - for file, stats in file_changes.items(): - i_total += stats["insertions"] - d_total += stats["deletions"] - l_total += stats["lines"] - table.add_row( - file, - str(stats["insertions"]), - str(stats["deletions"]), - str(stats["lines"]), - ) - - table.add_row("Total", str(i_total), str(d_total), str(l_total)) + status = update(self.bot, self.logger) + if status: console = Console() with console.capture() as capture: - console.print(table) + console.print(status.table) self.logger.debug(capture.get()) self.logger.debug(len(capture.get())) - new = "\n".join(loaded) - removed = "\n".join(unloaded) - changed = "\n".join(reloaded) + added = "\n".join(status.added) + removed = "\n".join(status.removed) + changed = "\n".join(status.changed) fields = [ - EmbedField(name="Old Commit", value=current_hash), - EmbedField(name="New Commit", value=remote_hash), + EmbedField(name="Old Commit", value=status.old_hash), + EmbedField(name="New Commit", value=status.new_hash), ] - if loaded: - fields.append(EmbedField(name="New Modules", value=f"```\n{new}\n```")) + if added: + fields.append(EmbedField(name="New Modules", value=f"```\n{added}\n```")) if removed: fields.append(EmbedField(name="Removed Modules", value=f"```\n{removed}\n```")) if changed: @@ -206,11 +108,10 @@ class BotutilCog(Scale): try: await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) except HTTPException: - await ctx.reply(f"Total Changes: {l_total}", embed=embed) + await ctx.reply(f"Total Changes: {status.total_lines}", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) - embed.set_footer(text=current_hash) embed.set_thumbnail(url="https://dev.zevaryx.com/git.png") await ctx.reply(embed=embed) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py new file mode 100644 index 0000000..b1a1cad --- /dev/null +++ b/jarvis/utils/updates.py @@ -0,0 +1,187 @@ +"""JARVIS update handler.""" +import asyncio +import importlib +import inspect +import logging +from dataclasses import dataclass +from pkgutil import iter_modules +from types import ModuleType +from typing import TYPE_CHECKING, Callable, Dict, List, Optional + +import git +from dis_snek.client.utils.misc_utils import find, find_all +from dis_snek.models.snek.application_commands import SlashCommand +from dis_snek.models.snek.scale import Scale +from rich.table import Table + +import jarvis.cogs + +if TYPE_CHECKING: + from logging import Logger + + from dis_snek.client.client import Snake + + +@dataclass +class UpdateResult: + """JARVIS update result.""" + + old_hash: str + new_hash: str + table: Table + added: List[str] + removed: List[str] + changed: List[str] + inserted_lines: int + deleted_lines: int + total_lines: int + + +def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: + """Get all SlashCommands from a specified module.""" + commands = {} + for item in iter_modules(module.__path__): + new_module = importlib.import_module(f"{module.__name__}.{item.name}") + if item.ispkg: + if cmds := get_all_commands(new_module): + commands.update(cmds) + else: + inspect_result = inspect.getmembers(new_module) + cogs = [] + for _, val in inspect_result: + if inspect.isclass(val) and issubclass(val, Scale) and val is not Scale: + cogs.append(val) + for cog in cogs: + values = cog.__dict__.values() + commands[cog.__module__] = find_all(lambda x: isinstance(x, SlashCommand), values) + return {k: v for k, v in commands.items() if v} + + +async def update(bot: "Snake", logger: "Logger" = None) -> Optional[UpdateResult]: + """ + Update JARVIS and return an UpdateResult. + + Args: + bot: Bot instance + logger: Logger instance + + Returns: + UpdateResult object + """ + if not logger: + logger = logging.getLogger(__name__) + repo = git.Repo(".") + current_hash = repo.head.object.hexsha + origin = repo.remotes.origin + origin.fetch() + remote_hash = origin.refs[repo.active_branch.name].object.hexsha + + if current_hash != remote_hash: + logger.info(f"Updating from {current_hash} to {remote_hash}") + current_commands = get_all_commands() + + changes = origin.pull() + logger.info(f"Pulled {len(changes)} changes") + await asyncio.sleep(3) + + new_commands = get_all_commands() + + logger.info("Checking if any modules need reloaded...") + + reloaded = [] + loaded = [] + unloaded = [] + + logger.debug("Checking for removed cogs") + for module in current_commands.keys(): + if module not in new_commands: + logger.debug(f"Module {module} removed after update") + bot.unload_extension(module) + unloaded.append(module) + + logger.debug("Checking for new/modified commands") + for module, commands in new_commands.items(): + logger.debug(f"Processing {module}") + if module not in current_commands: + bot.load_extension(module) + loaded.append(module) + elif len(current_commands[module]) != len(commands): + bot.reload_extension(module) + reloaded.append(module) + else: + for command in commands: + old_command = find( + lambda x: x.resolved_name == command.resolved_name, current_commands[module] + ) + + # Extract useful info + old_args = old_command.options + if old_args: + old_arg_names = [x.name for x in old_args] + new_args = command.options + if new_args: + new_arg_names = [x.name for x in new_args] + + # No changes + if not old_args and not new_args: + continue + + # Check if number arguments have changed + if len(old_args) != len(new_args): + bot.reload_extension(module) + reloaded.append(module) + elif any(x not in old_arg_names for x in new_arg_names) or any( + x not in new_arg_names for x in old_arg_names + ): + bot.reload_extension(module) + reloaded.append(module) + elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): + bot.reload_extension(module) + reloaded.append(module) + + file_changes = {} + for change in sorted(changes, key=lambda x: x.commit.committed_datetime, reverse=True): + if change.commit.hexsha == current_hash: + break + files = change.commit.stats.files + for file, stats in files.items(): + if file not in file_changes: + file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} + for k, v in stats.items(): + file_changes[file][k] += v + + table = Table(title="File Changes") + + table.add_column("File", justify="left", style="white", no_wrap=True) + table.add_column("Insertions", justify="center", style="green") + table.add_column("Deletions", justify="center", style="red") + table.add_column("Lines", justify="center", style="magenta") + + i_total = 0 + d_total = 0 + l_total = 0 + for file, stats in file_changes.items(): + i_total += stats["insertions"] + d_total += stats["deletions"] + l_total += stats["lines"] + table.add_row( + file, + str(stats["insertions"]), + str(stats["deletions"]), + str(stats["lines"]), + ) + + table.add_row("Total", str(i_total), str(d_total), str(l_total)) + + return UpdateResult( + table=table, + old_hash=current_hash, + new_hash=remote_hash, + added=loaded, + removed=unloaded, + changed=reloaded, + inserted_lines=i_total, + deleted_lines=d_total, + total_lines=l_total, + ) + return None From 3bafd25cabc819fc0e36d9a952c47ef4a5039025 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:09:50 -0600 Subject: [PATCH 275/365] Abstract git change calculator --- jarvis/utils/updates.py | 91 +++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index b1a1cad..33c104f 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -57,6 +57,54 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: return {k: v for k, v in commands.items() if v} +def get_git_changes() -> dict: + """Get all Git changes""" + repo = git.Repo(".") + current_hash = repo.head.object.hexsha + origin = repo.remotes.origin + changes = origin.fetch() + + file_changes = {} + for change in changes: + if change.commit.hexsha == current_hash: + break + files = change.commit.stats.files + for file, stats in files.items(): + if file not in file_changes: + file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} + for k, v in stats.items(): + file_changes[file][k] += v + + table = Table(title="File Changes") + + table.add_column("File", justify="left", style="white", no_wrap=True) + table.add_column("Insertions", justify="center", style="green") + table.add_column("Deletions", justify="center", style="red") + table.add_column("Lines", justify="center", style="magenta") + + i_total = 0 + d_total = 0 + l_total = 0 + for file, stats in file_changes.items(): + i_total += stats["insertions"] + d_total += stats["deletions"] + l_total += stats["lines"] + table.add_row( + file, + str(stats["insertions"]), + str(stats["deletions"]), + str(stats["lines"]), + ) + + table.add_row("Total", str(i_total), str(d_total), str(l_total)) + return { + "table": table, + "inserted_lines": i_total, + "deleted_lines": d_total, + "total_lines": l_total, + } + + async def update(bot: "Snake", logger: "Logger" = None) -> Optional[UpdateResult]: """ Update JARVIS and return an UpdateResult. @@ -79,9 +127,9 @@ async def update(bot: "Snake", logger: "Logger" = None) -> Optional[UpdateResult if current_hash != remote_hash: logger.info(f"Updating from {current_hash} to {remote_hash}") current_commands = get_all_commands() + changes = get_git_changes() - changes = origin.pull() - logger.info(f"Pulled {len(changes)} changes") + origin.pull() await asyncio.sleep(3) new_commands = get_all_commands() @@ -139,49 +187,12 @@ async def update(bot: "Snake", logger: "Logger" = None) -> Optional[UpdateResult bot.reload_extension(module) reloaded.append(module) - file_changes = {} - for change in sorted(changes, key=lambda x: x.commit.committed_datetime, reverse=True): - if change.commit.hexsha == current_hash: - break - files = change.commit.stats.files - for file, stats in files.items(): - if file not in file_changes: - file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} - for k, v in stats.items(): - file_changes[file][k] += v - - table = Table(title="File Changes") - - table.add_column("File", justify="left", style="white", no_wrap=True) - table.add_column("Insertions", justify="center", style="green") - table.add_column("Deletions", justify="center", style="red") - table.add_column("Lines", justify="center", style="magenta") - - i_total = 0 - d_total = 0 - l_total = 0 - for file, stats in file_changes.items(): - i_total += stats["insertions"] - d_total += stats["deletions"] - l_total += stats["lines"] - table.add_row( - file, - str(stats["insertions"]), - str(stats["deletions"]), - str(stats["lines"]), - ) - - table.add_row("Total", str(i_total), str(d_total), str(l_total)) - return UpdateResult( - table=table, old_hash=current_hash, new_hash=remote_hash, added=loaded, removed=unloaded, changed=reloaded, - inserted_lines=i_total, - deleted_lines=d_total, - total_lines=l_total, + **changes, ) return None From c5d228973e80c76bdcec9fe58a57a33d1462b1b3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:10:43 -0600 Subject: [PATCH 276/365] Fix update command --- jarvis/cogs/botutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 5b60d2c..45a0817 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -77,7 +77,7 @@ class BotutilCog(Scale): @msg_command(name="update") async def _update(self, ctx: MessageContext) -> None: - status = update(self.bot, self.logger) + status = await update(self.bot, self.logger) if status: console = Console() with console.capture() as capture: From e172346c3b4ad2353d481d656c375fbf49050a3e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:15:56 -0600 Subject: [PATCH 277/365] Change status formatting a little --- jarvis/cogs/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index 702e3f9..af14ddb 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -56,9 +56,9 @@ class UtilCog(Scale): fields = [] uptime = int(self.bot.start_time.timestamp()) - fields.append(EmbedField(name="dis-snek", value=const.__version__)) - fields.append(EmbedField(name="Version", value=jconst.__version__, inline=False)) - fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=False)) + fields.append(EmbedField(name="Version", value=jconst.__version__, inline=True)) + fields.append(EmbedField(name="dis-snek", value=const.__version__, inline=True)) + fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=True)) fields.append(EmbedField(name="Online Since", value=f"", inline=False)) num_domains = len(self.bot.phishing_domains) fields.append( From 8d1ecc030d5466a6dcfdbe8db75d08b8c8570c6e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:30:27 -0600 Subject: [PATCH 278/365] Dedicated logger for updates --- jarvis/cogs/botutil.py | 2 +- jarvis/utils/updates.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 45a0817..b71fe6c 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -77,7 +77,7 @@ class BotutilCog(Scale): @msg_command(name="update") async def _update(self, ctx: MessageContext) -> None: - status = await update(self.bot, self.logger) + status = await update(self.bot) if status: console = Console() with console.capture() as capture: diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 33c104f..65f597c 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -17,10 +17,10 @@ from rich.table import Table import jarvis.cogs if TYPE_CHECKING: - from logging import Logger - from dis_snek.client.client import Snake +logger = logging.getLogger(__name__) + @dataclass class UpdateResult: @@ -105,19 +105,16 @@ def get_git_changes() -> dict: } -async def update(bot: "Snake", logger: "Logger" = None) -> Optional[UpdateResult]: +async def update(bot: "Snake") -> Optional[UpdateResult]: """ Update JARVIS and return an UpdateResult. Args: bot: Bot instance - logger: Logger instance Returns: UpdateResult object """ - if not logger: - logger = logging.getLogger(__name__) repo = git.Repo(".") current_hash = repo.head.object.hexsha origin = repo.remotes.origin From 54aa0a0fad1c62dd34db32c596c4a9019e0ac8bd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:37:38 -0600 Subject: [PATCH 279/365] Change logic for processing git changes --- jarvis/utils/updates.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 65f597c..8016b7d 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -57,18 +57,17 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: return {k: v for k, v in commands.items() if v} -def get_git_changes() -> dict: +def get_git_changes(repo: git.Repo) -> dict: """Get all Git changes""" - repo = git.Repo(".") - current_hash = repo.head.object.hexsha - origin = repo.remotes.origin - changes = origin.fetch() + head = repo.head + current_hash = head.object.hexsha + tracking = head.tracking_branch() file_changes = {} - for change in changes: - if change.commit.hexsha == current_hash: + for commit in tracking.commit.iter_items(repo, f"{head.path}..{tracking.path}"): + if commit.hexsha == current_hash: break - files = change.commit.stats.files + files = commit.stats.files for file, stats in files.items(): if file not in file_changes: file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} From f10d808df99c6132175fd3b75fcf8adc827277d1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:38:41 -0600 Subject: [PATCH 280/365] Fix reddit user agent --- jarvis/cogs/reddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 2b2f024..9207ea6 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -21,7 +21,7 @@ from jarvis import const from jarvis.config import JarvisConfig from jarvis.utils.permissions import admin_or_permissions -DEFAULT_USER_AGENT = f"python:JARVIS-Tasks:{const.__version__} (by u/zevaryx)" +DEFAULT_USER_AGENT = f"python:JARVIS:{const.__version__} (by u/zevaryx)" class RedditCog(Scale): From 7e1f0735f482b480ab95d7357b70f153a0c93ef6 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:39:24 -0600 Subject: [PATCH 281/365] Fix update --- jarvis/utils/updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 8016b7d..6d88895 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -123,7 +123,7 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: if current_hash != remote_hash: logger.info(f"Updating from {current_hash} to {remote_hash}") current_commands = get_all_commands() - changes = get_git_changes() + changes = get_git_changes(repo) origin.pull() await asyncio.sleep(3) From 97928d1b86bc592083edd4f496b6af3770601068 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 15:54:01 -0600 Subject: [PATCH 282/365] Add more logging to updates --- jarvis/utils/updates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 6d88895..02de535 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -59,6 +59,7 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: def get_git_changes(repo: git.Repo) -> dict: """Get all Git changes""" + logger.debug("Getting all git changes") head = repo.head current_hash = head.object.hexsha tracking = head.tracking_branch() @@ -73,6 +74,7 @@ def get_git_changes(repo: git.Repo) -> dict: file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} for k, v in stats.items(): file_changes[file][k] += v + logger.debug(f"Found {len(file_changes)} changed files") table = Table(title="File Changes") @@ -94,6 +96,7 @@ def get_git_changes(repo: git.Repo) -> dict: str(stats["deletions"]), str(stats["lines"]), ) + logger.debug(f"{i_total} insertions, {d_total} deletions, {l_total} total") table.add_row("Total", str(i_total), str(d_total), str(l_total)) return { From e81fc9fbf8908307f4bdf1ef746a033c2c28ea28 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 16:08:20 -0600 Subject: [PATCH 283/365] Fix commit processing --- jarvis/utils/updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 02de535..b9d61c9 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -60,7 +60,7 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: def get_git_changes(repo: git.Repo) -> dict: """Get all Git changes""" logger.debug("Getting all git changes") - head = repo.head + head = repo.head.ref current_hash = head.object.hexsha tracking = head.tracking_branch() From 2282ca5cd86e37b8a85b675efe3241781e38bdc2 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 16:10:40 -0600 Subject: [PATCH 284/365] Add haskell formatting to cloc output --- jarvis/cogs/dev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 4d2b743..06dc1ec 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -272,7 +272,7 @@ class DevCog(Scale): output = subprocess.check_output( # noqa: S603, S607 ["tokei", "-C", "--sort", "code"] ).decode("UTF-8") - await ctx.send(f"```\n{output}\n```") + await ctx.send(f"```haskell\n{output}\n```") def setup(bot: Snake) -> None: From a8af888e7a318698ae3d89d8582d5dcdc0bcf5a3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 20:09:02 -0600 Subject: [PATCH 285/365] Add reddit hot command --- jarvis/cogs/reddit.py | 95 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 9207ea6..1456a09 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -1,13 +1,17 @@ """JARVIS Reddit cog.""" import asyncio import logging +from typing import List, Optional from asyncpraw import Reddit +from asyncpraw.models.reddit.submission import Submission +from asyncpraw.models.reddit.submission import Subreddit as Sub from asyncprawcore.exceptions import Forbidden, NotFound, Redirect from dis_snek import InteractionContext, Permissions, Scale, Snake from dis_snek.client.utils.misc_utils import get from dis_snek.models.discord.channel import ChannelTypes, GuildText from dis_snek.models.discord.components import ActionRow, Select, SelectOption +from dis_snek.models.discord.embed import Embed from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommand, @@ -19,6 +23,7 @@ from jarvis_core.db.models import Subreddit, SubredditFollow from jarvis import const from jarvis.config import JarvisConfig +from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions DEFAULT_USER_AGENT = f"python:JARVIS:{const.__version__} (by u/zevaryx)" @@ -34,6 +39,70 @@ class RedditCog(Scale): config.reddit["user_agent"] = config.reddit.get("user_agent", DEFAULT_USER_AGENT) self.api = Reddit(**config.reddit) + async def post_embeds(self, sub: Sub, post: Submission) -> Optional[List[Embed]]: + """ + Build a post embeds. + + Args: + post: Post to build embeds + """ + url = "https://reddit.com" + post.permalink + await post.author.load() + author_url = f"https://reddit.com/u/{post.author.name}" + images = [] + content = f"**{post.title}**" + if "url" in vars(post): + if any(post.url.endswith(x) for x in ["jpeg", "jpg", "png", "gif"]): + images = [post.url] + if "media_metadata" in vars(post): + for k, v in post.media_metadata.items(): + if v["status"] != "valid" or v["m"] not in ["image/jpg", "image/png", "image/gif"]: + continue + ext = v["m"].split("/")[-1] + i_url = f"https://i.redd.it/{k}.{ext}" + images.append(i_url) + if len(images) == 4: + break + + if "selftext" in vars(post) and post.selftext: + content += "\n\n" + post.selftext + if len(content) > 600: + content = content[:600] + "..." + content += f"\n\n[View this post]({url})" + + if not images and not content: + self.logger.debug(f"Post {post.id} had neither content nor images?") + return None + + color = "#FF4500" + if "primary_color" in vars(sub): + color = sub.primary_color + base_embed = build_embed( + title="", + description=content, + fields=[], + timestamp=post.created_utc, + url=url, + color=color, + ) + base_embed.set_author( + name="u/" + post.author.name, url=author_url, icon_url=post.author.icon_img + ) + base_embed.set_footer( + text="Reddit", icon_url="https://www.redditinc.com/assets/images/site/reddit-logo.png" + ) + + embeds = [base_embed] + + if len(images) > 0: + embeds[0].set_image(url=images[0]) + for image in images[1:4]: + embed = Embed(url=url) + embed.set_image(url=image) + embeds.append(embed) + + return embeds + reddit = SlashCommand(name="reddit", description="Manage Reddit follows") @reddit.subcommand(sub_cmd_name="follow", sub_cmd_description="Follow a Subreddit") @@ -160,6 +229,32 @@ class RedditCog(Scale): component.disabled = True await message.edit(components=components) + @reddit.subcommand(sub_cmd_name="hot", sub_cmd_description="Get the hot post of a subreddit") + @slash_option( + name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True + ) + async def _subreddit_hot(self, ctx: InteractionContext, name: str) -> None: + name = name.replace("r/", "") + if len(name) > 20 or len(name) < 3: + await ctx.send("Invalid Subreddit name", ephemeral=True) + return + try: + subreddit = await self.api.subreddit(name) + await subreddit.load() + except (NotFound, Forbidden, Redirect) as e: + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in hot") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + try: + hot = [x async for x in subreddit.hot(limit=1)][0] + except Exception as e: + self.logger.error(f"Failed to get hot from {name}", exc_info=e) + await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) + return + + embeds = await self.post_embeds(subreddit, hot) + await ctx.send(embeds=embeds) + def setup(bot: Snake) -> None: """Add RedditCog to JARVIS""" From 9af93435e732c8b6e61bb65134f51939cfafda46 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 22:36:50 -0600 Subject: [PATCH 286/365] Add reddit top command --- jarvis/cogs/reddit.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 1456a09..4d6a137 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -15,6 +15,7 @@ from dis_snek.models.discord.embed import Embed from dis_snek.models.snek.application_commands import ( OptionTypes, SlashCommand, + SlashCommandChoice, slash_option, ) from dis_snek.models.snek.command import check @@ -255,6 +256,46 @@ class RedditCog(Scale): embeds = await self.post_embeds(subreddit, hot) await ctx.send(embeds=embeds) + @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the hot post of a subreddit") + @slash_option( + name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True + ) + @slash_option( + name="time", + description="Top time", + opt_type=OptionTypes.STRING, + required=False, + choices=[ + SlashCommandChoice(name="All", value="all"), + SlashCommandChoice(name="Day", value="day"), + SlashCommandChoice(name="Hour", value="hour"), + SlashCommandChoice(name="Month", value="month"), + SlashCommandChoice(name="Week", value="week"), + SlashCommandChoice(name="Year", value="year"), + ], + ) + async def _subreddit_top(self, ctx: InteractionContext, name: str, time: str = "all") -> None: + name = name.replace("r/", "") + if len(name) > 20 or len(name) < 3: + await ctx.send("Invalid Subreddit name", ephemeral=True) + return + try: + subreddit = await self.api.subreddit(name) + await subreddit.load() + except (NotFound, Forbidden, Redirect) as e: + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in hot") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + try: + hot = [x async for x in subreddit.top(time_filter=time, limit=1)][0] + except Exception as e: + self.logger.error(f"Failed to get hot from {name}", exc_info=e) + await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) + return + + embeds = await self.post_embeds(subreddit, hot) + await ctx.send(embeds=embeds) + def setup(bot: Snake) -> None: """Add RedditCog to JARVIS""" From a6a0a5364cc4a6d6cd4766ab7446574bc1071257 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 22:41:36 -0600 Subject: [PATCH 287/365] Add reddit random command --- jarvis/cogs/reddit.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 4d6a137..e632d33 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -256,7 +256,7 @@ class RedditCog(Scale): embeds = await self.post_embeds(subreddit, hot) await ctx.send(embeds=embeds) - @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the hot post of a subreddit") + @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the top post of a subreddit") @slash_option( name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True ) @@ -283,7 +283,7 @@ class RedditCog(Scale): subreddit = await self.api.subreddit(name) await subreddit.load() except (NotFound, Forbidden, Redirect) as e: - self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in hot") + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in top") await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return try: @@ -296,6 +296,34 @@ class RedditCog(Scale): embeds = await self.post_embeds(subreddit, hot) await ctx.send(embeds=embeds) + @reddit.subcommand( + sub_cmd_name="random", sub_cmd_description="Get a random post of a subreddit" + ) + @slash_option( + name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True + ) + async def _subreddit_random(self, ctx: InteractionContext, name: str) -> None: + name = name.replace("r/", "") + if len(name) > 20 or len(name) < 3: + await ctx.send("Invalid Subreddit name", ephemeral=True) + return + try: + subreddit = await self.api.subreddit(name) + await subreddit.load() + except (NotFound, Forbidden, Redirect) as e: + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in random") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + try: + hot = await subreddit.random() + except Exception as e: + self.logger.error(f"Failed to get hot from {name}", exc_info=e) + await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) + return + + embeds = await self.post_embeds(subreddit, hot) + await ctx.send(embeds=embeds) + def setup(bot: Snake) -> None: """Add RedditCog to JARVIS""" From 56427993fbeda4476f9ec71f19041e80bd35ca32 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 22:44:03 -0600 Subject: [PATCH 288/365] Defer reddit random to allow for API to return result --- jarvis/cogs/reddit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index e632d33..b9a52f0 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -303,6 +303,7 @@ class RedditCog(Scale): name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True ) async def _subreddit_random(self, ctx: InteractionContext, name: str) -> None: + await ctx.defer() name = name.replace("r/", "") if len(name) > 20 or len(name) < 3: await ctx.send("Invalid Subreddit name", ephemeral=True) From 415089224536d4c4488c7c97a2f1375bd4e90394 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 23:00:28 -0600 Subject: [PATCH 289/365] Use proper functions to re-sync commands on update --- jarvis/utils/updates.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index b9d61c9..d2cf331 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -143,17 +143,17 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: for module in current_commands.keys(): if module not in new_commands: logger.debug(f"Module {module} removed after update") - bot.unload_extension(module) + bot.shed_scale(module) unloaded.append(module) logger.debug("Checking for new/modified commands") for module, commands in new_commands.items(): logger.debug(f"Processing {module}") if module not in current_commands: - bot.load_extension(module) + bot.grow_scale(module) loaded.append(module) elif len(current_commands[module]) != len(commands): - bot.reload_extension(module) + bot.regrow_scale(module) reloaded.append(module) else: for command in commands: @@ -175,15 +175,15 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: # Check if number arguments have changed if len(old_args) != len(new_args): - bot.reload_extension(module) + bot.regrow_scale(module) reloaded.append(module) elif any(x not in old_arg_names for x in new_arg_names) or any( x not in new_arg_names for x in old_arg_names ): - bot.reload_extension(module) + bot.regrow_scale(module) reloaded.append(module) elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - bot.reload_extension(module) + bot.regrow_scale(module) reloaded.append(module) return UpdateResult( From af42b385ab1e96ed2cfce1c4db261ffec3e17e9f Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Sun, 1 May 2022 23:06:52 -0600 Subject: [PATCH 290/365] Add reddit rising, rename variables --- jarvis/cogs/reddit.py | 47 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index b9a52f0..eb148f7 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -247,13 +247,13 @@ class RedditCog(Scale): await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return try: - hot = [x async for x in subreddit.hot(limit=1)][0] + post = [x async for x in subreddit.hot(limit=1)][0] except Exception as e: - self.logger.error(f"Failed to get hot from {name}", exc_info=e) + self.logger.error(f"Failed to get post from {name}", exc_info=e) await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) return - embeds = await self.post_embeds(subreddit, hot) + embeds = await self.post_embeds(subreddit, post) await ctx.send(embeds=embeds) @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the top post of a subreddit") @@ -287,13 +287,13 @@ class RedditCog(Scale): await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return try: - hot = [x async for x in subreddit.top(time_filter=time, limit=1)][0] + post = [x async for x in subreddit.top(time_filter=time, limit=1)][0] except Exception as e: - self.logger.error(f"Failed to get hot from {name}", exc_info=e) + self.logger.error(f"Failed to get post from {name}", exc_info=e) await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) return - embeds = await self.post_embeds(subreddit, hot) + embeds = await self.post_embeds(subreddit, post) await ctx.send(embeds=embeds) @reddit.subcommand( @@ -316,13 +316,42 @@ class RedditCog(Scale): await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) return try: - hot = await subreddit.random() + post = await subreddit.random() except Exception as e: - self.logger.error(f"Failed to get hot from {name}", exc_info=e) + self.logger.error(f"Failed to get post from {name}", exc_info=e) await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) return - embeds = await self.post_embeds(subreddit, hot) + embeds = await self.post_embeds(subreddit, post) + await ctx.send(embeds=embeds) + + @reddit.subcommand( + sub_cmd_name="rising", sub_cmd_description="Get a rising post of a subreddit" + ) + @slash_option( + name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True + ) + async def _subreddit_rising(self, ctx: InteractionContext, name: str) -> None: + await ctx.defer() + name = name.replace("r/", "") + if len(name) > 20 or len(name) < 3: + await ctx.send("Invalid Subreddit name", ephemeral=True) + return + try: + subreddit = await self.api.subreddit(name) + await subreddit.load() + except (NotFound, Forbidden, Redirect) as e: + self.logger.debug(f"Subreddit {name} raised {e.__class__.__name__} in rising") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + try: + post = [x async for x in subreddit.rising(limit=1)][0] + except Exception as e: + self.logger.error(f"Failed to get post from {name}", exc_info=e) + await ctx.send("Well, this is awkward. Something went wrong", ephemeral=True) + return + + embeds = await self.post_embeds(subreddit, post) await ctx.send(embeds=embeds) From d76d030bfd67100f638a7aebb3ea0d269a9d5a48 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 01:18:33 -0600 Subject: [PATCH 291/365] perflint and pylint recommendations --- jarvis/__init__.py | 47 +++++++++++++------------ jarvis/client.py | 18 +++++----- jarvis/cogs/admin/ban.py | 7 +--- jarvis/cogs/admin/kick.py | 8 +---- jarvis/cogs/admin/mute.py | 7 +--- jarvis/cogs/admin/warning.py | 7 +--- jarvis/cogs/botutil.py | 10 +++--- jarvis/cogs/image.py | 2 +- jarvis/cogs/starboard.py | 2 +- jarvis/cogs/verify.py | 6 ++-- jarvis/config.py | 2 +- jarvis/utils/__init__.py | 33 ++---------------- jarvis/utils/cogs.py | 50 ++++++--------------------- jarvis/utils/embeds.py | 2 +- jarvis/utils/updates.py | 67 ++++++++++++++++++++---------------- 15 files changed, 100 insertions(+), 168 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index e1ea96a..be4817f 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -7,34 +7,37 @@ from dis_snek import Intents from jarvis_core.db import connect from jarvis_core.log import get_logger -from jarvis import const, utils +from jarvis import const from jarvis.client import Jarvis +from jarvis.cogs import __path__ as cogs_path from jarvis.config import JarvisConfig +from jarvis.utils import get_extensions __version__ = const.__version__ -jconfig = JarvisConfig.from_yaml() -logger = get_logger("jarvis", show_locals=jconfig.log_level == "DEBUG") -logger.setLevel(jconfig.log_level) -file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") -file_handler.setFormatter( - logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s") -) -logger.addHandler(file_handler) - -intents = Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT -restart_ctx = None - -jarvis = Jarvis( - intents=intents, - sync_interactions=jconfig.sync, - delete_unused_application_cmds=True, - send_command_tracebacks=False, -) - async def run() -> None: """Run JARVIS""" + jconfig = JarvisConfig.from_yaml() + logger = get_logger("jarvis", show_locals=jconfig.log_level == "DEBUG") + logger.setLevel(jconfig.log_level) + file_handler = logging.FileHandler(filename="jarvis.log", encoding="UTF-8", mode="w") + file_handler.setFormatter( + logging.Formatter("[%(asctime)s] [%(name)s] [%(levelname)8s] %(message)s") + ) + logger.addHandler(file_handler) + + intents = ( + Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT + ) + + jarvis = Jarvis( + intents=intents, + sync_interactions=jconfig.sync, + delete_unused_application_cmds=True, + send_command_tracebacks=False, + ) + if jconfig.log_level == "DEBUG": jurigged.watch() if jconfig.rook_token: @@ -46,9 +49,9 @@ async def run() -> None: # jconfig.get_db_config() logger.debug("Loading extensions") - for extension in utils.get_extensions(): + for extension in get_extensions(cogs_path): jarvis.load_extension(extension) - logger.debug(f"Loaded {extension}") + logger.debug("Loaded %s", extension) jarvis.max_messages = jconfig.max_messages logger.debug("Running JARVIS") diff --git a/jarvis/client.py b/jarvis/client.py index 7d192f4..b9dd128 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -270,7 +270,7 @@ class Jarvis(Snake): channel = await guild.fetch_channel(log.channel) embed = build_embed( title="Member Left", - desciption=f"{user.username}#{user.discriminator} left {guild.name}", + description=f"{user.username}#{user.discriminator} left {guild.name}", fields=[], ) embed.set_author(name=user.username, icon_url=user.avatar.url) @@ -394,12 +394,9 @@ class Jarvis(Snake): rolepings = await Roleping.find(q(guild=message.guild.id, active=True)).to_list(None) # Get all role IDs involved with message - roles = [] - async for mention in message.mention_roles: - roles.append(mention.id) + roles = [x.id async for x in message.mention_roles] async for mention in message.mention_users: - for role in mention.roles: - roles.append(role.id) + roles += [x.id for x in mention.roles] if not roles: return @@ -417,12 +414,15 @@ class Jarvis(Snake): user_is_admin = message.author.has_permission(Permissions.ADMINISTRATOR) # Check if user in a bypass list + def check_has_role(roleping: Roleping) -> bool: + return any(role.id in roleping.bypass["roles"] for role in message.author.roles) + user_has_bypass = False for roleping in rolepings: if message.author.id in roleping.bypass["users"]: user_has_bypass = True break - if any(role.id in roleping.bypass["roles"] for role in message.author.roles): + if check_has_role(roleping): user_has_bypass = True break @@ -553,7 +553,7 @@ class Jarvis(Snake): ) await channel.send(embed=embed) except Exception as e: - self.logger.warn( + self.logger.warning( f"Failed to process edit {before.guild.id}/{before.channel.id}/{before.id}: {e}" ) if not isinstance(after.channel, DMChannel) and not after.author.bot: @@ -629,6 +629,6 @@ class Jarvis(Snake): ) await channel.send(embed=embed) except Exception as e: - self.logger.warn( + self.logger.warning( f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}" ) diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 223b37e..43fcac1 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,8 +1,7 @@ """JARVIS BanCog.""" -import logging import re -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import find, find_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -26,10 +25,6 @@ from jarvis.utils.permissions import admin_or_permissions class BanCog(ModcaseCog): """JARVIS BanCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - self.logger = logging.getLogger(__name__) - async def discord_apply_ban( self, ctx: InteractionContext, diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index 618e61b..e1684a8 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,7 +1,5 @@ """JARVIS KickCog.""" -import logging - -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.user import User from dis_snek.models.snek.application_commands import ( @@ -20,10 +18,6 @@ from jarvis.utils.permissions import admin_or_permissions class KickCog(ModcaseCog): """JARVIS KickCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - self.logger = logging.getLogger(__name__) - @slash_command(name="kick", description="Kick a user") @slash_option(name="user", description="User to kick", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index b659265..a746e12 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -1,11 +1,10 @@ """JARVIS MuteCog.""" import asyncio -import logging from datetime import datetime, timedelta, timezone from dateparser import parse from dateparser_data.settings import default_parsers -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.client.errors import Forbidden from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.modal import InputText, Modal, TextStyles @@ -29,10 +28,6 @@ from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): """JARVIS MuteCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - self.logger = logging.getLogger(__name__) - async def _apply_timeout( self, ctx: InteractionContext, user: Member, reason: str, until: datetime ) -> None: diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index c5ddbaf..de04ac2 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,8 +1,7 @@ """JARVIS WarningCog.""" -import logging from datetime import datetime, timedelta, timezone -from dis_snek import InteractionContext, Permissions, Snake +from dis_snek import InteractionContext, Permissions from dis_snek.client.utils.misc_utils import get_all from dis_snek.ext.paginators import Paginator from dis_snek.models.discord.embed import EmbedField @@ -25,10 +24,6 @@ from jarvis.utils.permissions import admin_or_permissions class WarningCog(ModcaseCog): """JARVIS WarningCog.""" - def __init__(self, bot: Snake): - super().__init__(bot) - self.logger = logging.getLogger(__name__) - @slash_command(name="warn", description="Warn a user") @slash_option(name="user", description="User to warn", opt_type=OptionTypes.USER, required=True) @slash_option( diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index b71fe6c..d72d9fd 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -5,11 +5,11 @@ from io import BytesIO import psutil from aiofile import AIOFile, LineReader -from dis_snek import MessageContext, Scale, Snake +from dis_snek import Scale, Snake from dis_snek.client.errors import HTTPException from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File -from molter import msg_command +from molter import MessageContext, msg_command from rich.console import Console from jarvis.utils import build_embed @@ -64,13 +64,13 @@ class BotutilCog(Scale): async def _sysinfo(self, ctx: MessageContext) -> None: st_ts = int(self.bot.start_time.timestamp()) ut_ts = int(psutil.boot_time()) - fields = [ + fields = ( EmbedField(name="Operation System", value=platform.system() or "Unknown", inline=False), EmbedField(name="Version", value=platform.release() or "N/A", inline=False), EmbedField(name="System Start Time", value=f" ()"), EmbedField(name="Python Version", value=platform.python_version()), EmbedField(name="Bot Start Time", value=f" ()"), - ] + ) embed = build_embed(title="System Info", description="", fields=fields) embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) @@ -108,7 +108,7 @@ class BotutilCog(Scale): try: await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) except HTTPException: - await ctx.reply(f"Total Changes: {status.total_lines}", embed=embed) + await ctx.reply(f"Total Changes: {status.lines['total_lines']}", embed=embed) else: embed = build_embed(title="Update Status", description="No changes applied", fields=[]) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 048bb1b..775d191 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -85,7 +85,7 @@ class ImageCog(Scale): if tgt_size > unconvert_bytesize(8, "MB"): await ctx.send("Target too large to send. Please make target < 8MB", ephemeral=True) return - elif tgt_size < 1024: + if tgt_size < 1024: await ctx.send("Sizes < 1KB are extremely unreliable and are disabled", ephemeral=True) return diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 85383c7..3ced678 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -135,7 +135,7 @@ class StarboardCog(Scale): if c and isinstance(c, GuildText): channel_list.append(c) else: - self.logger.warn( + self.logger.warning( f"Starboard {starboard.channel} no longer valid in {ctx.guild.name}" ) to_delete.append(starboard) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index d7dda8e..dcd0b95 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -79,14 +79,16 @@ class VerifyCog(Scale): role = await ctx.guild.fetch_role(setting.value) await ctx.author.add_role(role, reason="Verification passed") except AttributeError: - self.logger.warn("Verified role deleted before verification finished") + self.logger.warning("Verified role deleted before verification finished") setting = await Setting.find_one(q(guild=ctx.guild.id, setting="unverified")) if setting: try: role = await ctx.guild.fetch_role(setting.value) await ctx.author.remove_role(role, reason="Verification passed") except AttributeError: - self.logger.warn("Unverified role deleted before verification finished") + self.logger.warning( + "Unverified role deleted before verification finished" + ) await response.context.edit_origin( content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", diff --git a/jarvis/config.py b/jarvis/config.py index ba7d633..50b9310 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -12,7 +12,7 @@ except ImportError: class JarvisConfig(CConfig): - REQUIRED = ["token", "mongo", "urls"] + REQUIRED = ("token", "mongo", "urls") OPTIONAL = { "sync": False, "log_level": "WARNING", diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 2937c8a..aec9c96 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -1,24 +1,14 @@ """JARVIS Utility Functions.""" -import importlib -import inspect from datetime import datetime, timezone from pkgutil import iter_modules -from types import ModuleType -from typing import Callable, Dict import git -from dis_snek.client.utils.misc_utils import find_all from dis_snek.models.discord.embed import Embed, EmbedField from dis_snek.models.discord.guild import AuditLogEntry from dis_snek.models.discord.user import Member -from dis_snek.models.snek import Scale -from dis_snek.models.snek.application_commands import SlashCommand -import jarvis.cogs from jarvis.config import get_config -__all__ = ["cachecog", "permissions"] - def build_embed( title: str, @@ -71,30 +61,11 @@ def modlog_embed( return embed -def get_extensions(path: str = jarvis.cogs.__path__) -> list: +def get_extensions(path: str) -> list: """Get JARVIS cogs.""" config = get_config() vals = config.cogs or [x.name for x in iter_modules(path)] - return ["jarvis.cogs.{}".format(x) for x in vals] - - -def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: - commands = {} - for item in iter_modules(module.__path__): - new_module = importlib.import_module(f"{module.__name__}.{item.name}") - if item.ispkg: - if cmds := get_all_commands(new_module): - commands.update(cmds) - else: - inspect_result = inspect.getmembers(new_module) - cogs = [] - for _, val in inspect_result: - if inspect.isclass(val) and issubclass(val, Scale) and val is not Scale: - cogs.append(val) - for cog in cogs: - values = cog.__dict__.values() - commands[cog.__module__] = find_all(lambda x: isinstance(x, SlashCommand), values) - return {k: v for k, v in commands.items() if v} + return [f"jarvis.cogs.{x}" for x in vals] def update() -> int: diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 7a342d9..ce44146 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,11 +1,8 @@ """Cog wrapper for command caching.""" -from datetime import datetime, timedelta, timezone +import logging from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import find from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q from jarvis_core.db.models import ( Action, @@ -24,42 +21,15 @@ MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} IGNORE_COMMANDS = {"Ban": ["bans"], "Kick": [], "Mute": ["unmute"], "Warning": ["warnings"]} -class CacheCog(Scale): - """Cog wrapper for command caching.""" - - def __init__(self, bot: Snake): - self.bot = bot - self.cache = {} - self._expire_interaction.start() - - def check_cache(self, ctx: InteractionContext, **kwargs: dict) -> dict: - """Check the cache.""" - if not kwargs: - kwargs = {} - return find( - lambda x: x["command"] == ctx.subcommand_name # noqa: W503 - and x["user"] == ctx.author.id # noqa: W503 - and x["guild"] == ctx.guild.id # noqa: W503 - and all(x[k] == v for k, v in kwargs.items()), # noqa: W503 - self.cache.values(), - ) - - @Task.create(IntervalTrigger(minutes=1)) - async def _expire_interaction(self) -> None: - keys = list(self.cache.keys()) - for key in keys: - if self.cache[key]["timeout"] <= datetime.now(tz=timezone.utc) + timedelta(minutes=1): - del self.cache[key] - - class ModcaseCog(Scale): """Cog wrapper for moderation case logging.""" def __init__(self, bot: Snake): self.bot = bot + self.logger = logging.getLogger(__name__) self.add_scale_postrun(self.log) - async def log(self, ctx: InteractionContext, *args: list, **kwargs: dict) -> None: + async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None: """ Log a moderation activity in a moderation case. @@ -71,31 +41,31 @@ class ModcaseCog(Scale): if name in MODLOG_LOOKUP and ctx.command not in IGNORE_COMMANDS[name]: user = kwargs.pop("user", None) if not user and not ctx.target_id: - self.logger.warn(f"Admin action {name} missing user, exiting") + self.logger.warning("Admin action %s missing user, exiting", name) return - elif ctx.target_id: + if ctx.target_id: user = ctx.target coll = MODLOG_LOOKUP.get(name, None) if not coll: - self.logger.warn(f"Unsupported action {name}, exiting") + self.logger.warning("Unsupported action %s, exiting", name) return action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) if not action: - self.logger.warn(f"Missing action {name}, exiting") + self.logger.warning("Missing action %s, exiting", name) return action = Action(action_type=name.lower(), parent=action.id) note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() notify = await Setting.find_one(q(guild=ctx.guild.id, setting="notify", value=True)) - if notify and name not in ["Kick", "Ban"]: # Ignore Kick and Ban, as these are unique - fields = [ + if notify and name not in ("Kick", "Ban"): # Ignore Kick and Ban, as these are unique + fields = ( EmbedField(name="Action Type", value=name, inline=False), EmbedField( name="Reason", value=kwargs.get("reason", None) or "N/A", inline=False ), - ] + ) embed = build_embed( title="Admin action taken", description=f"Admin action has been taken against you in {ctx.guild.name}", diff --git a/jarvis/utils/embeds.py b/jarvis/utils/embeds.py index c7a9126..c0fcba1 100644 --- a/jarvis/utils/embeds.py +++ b/jarvis/utils/embeds.py @@ -13,7 +13,7 @@ def warning_embed(user: Member, reason: str) -> Embed: user: User to warn reason: Warning reason """ - fields = [EmbedField(name="Reason", value=reason, inline=False)] + fields = (EmbedField(name="Reason", value=reason, inline=False),) embed = build_embed( title="Warning", description=f"{user.mention} has been warned", fields=fields ) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index d2cf331..da9ba68 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -1,11 +1,11 @@ """JARVIS update handler.""" import asyncio -import importlib -import inspect import logging from dataclasses import dataclass +from importlib import import_module +from inspect import getmembers, isclass from pkgutil import iter_modules -from types import ModuleType +from types import FunctionType, ModuleType from typing import TYPE_CHECKING, Callable, Dict, List, Optional import git @@ -19,7 +19,7 @@ import jarvis.cogs if TYPE_CHECKING: from dis_snek.client.client import Snake -logger = logging.getLogger(__name__) +_logger = logging.getLogger(__name__) @dataclass @@ -32,49 +32,57 @@ class UpdateResult: added: List[str] removed: List[str] changed: List[str] - inserted_lines: int - deleted_lines: int - total_lines: int + lines: Dict[str, int] def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: """Get all SlashCommands from a specified module.""" commands = {} + + def validate_ires(entry: tuple) -> bool: + return isclass(entry[1]) and issubclass(entry[1], Scale) and entry[1] is not Scale + + def validate_cog(cog: FunctionType) -> bool: + return isinstance(cog, SlashCommand) + for item in iter_modules(module.__path__): - new_module = importlib.import_module(f"{module.__name__}.{item.name}") + new_module = import_module(f"{module.__name__}.{item.name}") if item.ispkg: if cmds := get_all_commands(new_module): commands.update(cmds) else: - inspect_result = inspect.getmembers(new_module) - cogs = [] - for _, val in inspect_result: - if inspect.isclass(val) and issubclass(val, Scale) and val is not Scale: - cogs.append(val) - for cog in cogs: - values = cog.__dict__.values() - commands[cog.__module__] = find_all(lambda x: isinstance(x, SlashCommand), values) + inspect_result = getmembers(new_module) + cogs = find_all(validate_ires, inspect_result) + commands.update( + { + commands[cog.__module__]: find_all(validate_cog, cog.__dict__.values()) + for cog in cogs + } + ) return {k: v for k, v in commands.items() if v} def get_git_changes(repo: git.Repo) -> dict: """Get all Git changes""" + logger = _logger logger.debug("Getting all git changes") - head = repo.head.ref - current_hash = head.object.hexsha - tracking = head.tracking_branch() + current_hash = repo.head.ref.object.hexsha + tracking = repo.head.ref.tracking_branch() file_changes = {} - for commit in tracking.commit.iter_items(repo, f"{head.path}..{tracking.path}"): + for commit in tracking.commit.iter_items(repo, f"{repo.head.ref.path}..{tracking.path}"): if commit.hexsha == current_hash: break files = commit.stats.files + file_changes.update( + {key: {"insertions": 0, "deletions": 0, "lines": 0} for key in files.keys()} + ) for file, stats in files.items(): if file not in file_changes: file_changes[file] = {"insertions": 0, "deletions": 0, "lines": 0} - for k, v in stats.items(): - file_changes[file][k] += v - logger.debug(f"Found {len(file_changes)} changed files") + for key, val in stats.items(): + file_changes[file][key] += val + logger.debug("Found %i changed files", len(file_changes)) table = Table(title="File Changes") @@ -96,14 +104,12 @@ def get_git_changes(repo: git.Repo) -> dict: str(stats["deletions"]), str(stats["lines"]), ) - logger.debug(f"{i_total} insertions, {d_total} deletions, {l_total} total") + logger.debug("%i insertions, %i deletions, %i total", i_total, d_total, l_total) table.add_row("Total", str(i_total), str(d_total), str(l_total)) return { "table": table, - "inserted_lines": i_total, - "deleted_lines": d_total, - "total_lines": l_total, + "lines": {"inserted_lines": i_total, "deleted_lines": d_total, "total_lines": l_total}, } @@ -117,6 +123,7 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: Returns: UpdateResult object """ + logger = _logger repo = git.Repo(".") current_hash = repo.head.object.hexsha origin = repo.remotes.origin @@ -124,7 +131,7 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: remote_hash = origin.refs[repo.active_branch.name].object.hexsha if current_hash != remote_hash: - logger.info(f"Updating from {current_hash} to {remote_hash}") + logger.info("Updating from %s to %s", current_hash, remote_hash) current_commands = get_all_commands() changes = get_git_changes(repo) @@ -142,13 +149,13 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: logger.debug("Checking for removed cogs") for module in current_commands.keys(): if module not in new_commands: - logger.debug(f"Module {module} removed after update") + logger.debug("Module %s removed after update", module) bot.shed_scale(module) unloaded.append(module) logger.debug("Checking for new/modified commands") for module, commands in new_commands.items(): - logger.debug(f"Processing {module}") + logger.debug("Processing %s", module) if module not in current_commands: bot.grow_scale(module) loaded.append(module) From d962f9e268ea7ddecc0d7f3502733bd652961356 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 01:57:15 -0600 Subject: [PATCH 292/365] Fix updates --- jarvis/utils/updates.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index da9ba68..726c777 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -6,7 +6,7 @@ from importlib import import_module from inspect import getmembers, isclass from pkgutil import iter_modules from types import FunctionType, ModuleType -from typing import TYPE_CHECKING, Callable, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional import git from dis_snek.client.utils.misc_utils import find, find_all @@ -39,8 +39,8 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: """Get all SlashCommands from a specified module.""" commands = {} - def validate_ires(entry: tuple) -> bool: - return isclass(entry[1]) and issubclass(entry[1], Scale) and entry[1] is not Scale + def validate_ires(entry: Any) -> bool: + return isclass(entry) and issubclass(entry, Scale) and entry is not Scale def validate_cog(cog: FunctionType) -> bool: return isinstance(cog, SlashCommand) @@ -52,13 +52,13 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: commands.update(cmds) else: inspect_result = getmembers(new_module) - cogs = find_all(validate_ires, inspect_result) - commands.update( - { - commands[cog.__module__]: find_all(validate_cog, cog.__dict__.values()) - for cog in cogs - } - ) + cogs = [] + for _, val in inspect_result: + if validate_ires(val): + cogs.append(val) + for cog in cogs: + values = cog.__dict__.values() + commands[cog.__module__] = find_all(lambda x: isinstance(x, SlashCommand), values) return {k: v for k, v in commands.items() if v} From a39f297669677723ddcac809e17d26a0410bcf9d Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 01:57:35 -0600 Subject: [PATCH 293/365] Remove molter --- jarvis/client.py | 8 ++++---- jarvis/cogs/botutil.py | 25 ++++++++++++------------- pyproject.toml | 1 - 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index b9dd128..1eb5223 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -19,7 +19,7 @@ from dis_snek.models.discord.channel import DMChannel from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.enums import Permissions from dis_snek.models.discord.message import Message -from dis_snek.models.snek.context import Context, InteractionContext, MessageContext +from dis_snek.models.snek.context import Context, InteractionContext, PrefixedContext from dis_snek.models.snek.tasks.task import Task from dis_snek.models.snek.tasks.triggers import IntervalTrigger from jarvis_core.db import q @@ -93,7 +93,7 @@ class Jarvis(Snake): if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) - elif isinstance(ctx, MessageContext): + elif isinstance(ctx, PrefixedContext): args = " ".join(args) self.logger.debug(f"Running command `{name}` with args: {args or 'None'}") @@ -152,7 +152,7 @@ class Jarvis(Snake): if isinstance(v, str) and len(v) > 100: v = v[97] + "..." arg_str += f"{v}\n" - elif isinstance(ctx, MessageContext): + elif isinstance(ctx, PrefixedContext): for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." @@ -214,7 +214,7 @@ class Jarvis(Snake): if len(v) > 100: v = v[:97] + "..." args.append(f"{KEY_FMT}{k}:{VAL_FMT}{v}{RESET}") - elif isinstance(ctx, MessageContext): + elif isinstance(ctx, PrefixedContext): for v in ctx.args: if isinstance(v, str) and len(v) > 100: v = v[97] + "..." diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index d72d9fd..2dd8649 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -5,11 +5,10 @@ from io import BytesIO import psutil from aiofile import AIOFile, LineReader -from dis_snek import Scale, Snake +from dis_snek import PrefixedContext, Scale, Snake, prefixed_command from dis_snek.client.errors import HTTPException from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File -from molter import MessageContext, msg_command from rich.console import Console from jarvis.utils import build_embed @@ -24,12 +23,12 @@ class BotutilCog(Scale): self.logger = logging.getLogger(__name__) self.add_scale_check(self.is_owner) - async def is_owner(self, ctx: MessageContext) -> bool: + async def is_owner(self, ctx: PrefixedContext) -> bool: """Checks if author is bot owner.""" return ctx.author.id == self.bot.owner.id - @msg_command(name="tail") - async def _tail(self, ctx: MessageContext, count: int = 10) -> None: + @prefixed_command(name="tail") + async def _tail(self, ctx: PrefixedContext, count: int = 10) -> None: lines = [] async with AIOFile("jarvis.log", "r") as af: async for line in LineReader(af): @@ -46,8 +45,8 @@ class BotutilCog(Scale): else: await ctx.reply(content=f"```\n{log}\n```") - @msg_command(name="log") - async def _log(self, ctx: MessageContext) -> None: + @prefixed_command(name="log") + async def _log(self, ctx: PrefixedContext) -> None: async with AIOFile("jarvis.log", "r") as af: with BytesIO() as file_bytes: raw = await af.read_bytes() @@ -56,12 +55,12 @@ class BotutilCog(Scale): log = File(file_bytes, file_name="jarvis.log") await ctx.reply(content="Here's the latest log", file=log) - @msg_command(name="crash") - async def _crash(self, ctx: MessageContext) -> None: + @prefixed_command(name="crash") + async def _crash(self, ctx: PrefixedContext) -> None: raise Exception("As you wish") - @msg_command(name="sysinfo") - async def _sysinfo(self, ctx: MessageContext) -> None: + @prefixed_command(name="sysinfo") + async def _sysinfo(self, ctx: PrefixedContext) -> None: st_ts = int(self.bot.start_time.timestamp()) ut_ts = int(psutil.boot_time()) fields = ( @@ -75,8 +74,8 @@ class BotutilCog(Scale): embed.set_image(url=self.bot.user.avatar.url) await ctx.send(embed=embed) - @msg_command(name="update") - async def _update(self, ctx: MessageContext) -> None: + @prefixed_command(name="update") + async def _update(self, ctx: PrefixedContext) -> None: status = await update(self.bot) if status: console = Console() diff --git a/pyproject.toml b/pyproject.toml index e0bca01..7e0bdaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ aiohttp = "^3.8.1" pastypy = "^1.0.1" dateparser = "^1.1.1" aiofile = "^3.7.4" -molter = "^0.11.0" asyncpraw = "^7.5.0" rook = "^0.1.170" rich = "^12.3.0" From 37bcf1476296173e414b00e64cd66b1f42d57e0e Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 02:16:09 -0600 Subject: [PATCH 294/365] Don't rely on exceptions, properly handle content lengths --- jarvis/cogs/botutil.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 2dd8649..8baff9a 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -6,7 +6,6 @@ from io import BytesIO import psutil from aiofile import AIOFile, LineReader from dis_snek import PrefixedContext, Scale, Snake, prefixed_command -from dis_snek.client.errors import HTTPException from dis_snek.models.discord.embed import EmbedField from dis_snek.models.discord.file import File from rich.console import Console @@ -104,9 +103,10 @@ class BotutilCog(Scale): embed.set_thumbnail(url="https://dev.zevaryx.com/git.png") self.logger.info("Updates applied") - try: - await ctx.reply(f"```ansi\n{capture.get()}\n```", embed=embed) - except HTTPException: + content = f"```ansi\n{capture.get()}\n```" + if len(content) < 3000: + await ctx.reply(content, embed=embed) + else: await ctx.reply(f"Total Changes: {status.lines['total_lines']}", embed=embed) else: From 51b7b0add5f117431434452645f6c46c3f4ceef0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 02:18:30 -0600 Subject: [PATCH 295/365] Don't log PrefixedCommands --- jarvis/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jarvis/client.py b/jarvis/client.py index 1eb5223..0269a57 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -200,6 +200,8 @@ class Jarvis(Snake): # Modlog async def on_command(self, ctx: Context) -> None: """NAFF on_command override.""" + if isinstance(ctx, PrefixedContext): + return if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: From 042401c56b76f0074f6680e5f17694a7120d06cf Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 02:21:22 -0600 Subject: [PATCH 296/365] Handle prefixed contexts better --- jarvis/client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 0269a57..667b80c 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -131,7 +131,8 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """NAFF on_command_error override.""" - self.logger.debug(f"Handling error in {ctx.invoked_name}: {error}") + name = ctx.invoked_name if isinstance(ctx, InteractionContext) else ctx.invoked_target + self.logger.debug(f"Handling error in {name}: {error}") if isinstance(error, CommandOnCooldown): await ctx.send(str(error), ephemeral=True) return @@ -164,7 +165,7 @@ class Jarvis(Snake): full_message = ERROR_MSG.format( guild_name=ctx.guild.name, error_time=error_time, - invoked_name=ctx.invoked_name, + invoked_name=name, arg_str=arg_str, callback_args=callback_args, callback_kwargs=callback_kwargs, @@ -200,9 +201,8 @@ class Jarvis(Snake): # Modlog async def on_command(self, ctx: Context) -> None: """NAFF on_command override.""" - if isinstance(ctx, PrefixedContext): - return - if not isinstance(ctx.channel, DMChannel) and ctx.invoked_name not in ["pw"]: + name = ctx.invoked_name if isinstance(ctx, InteractionContext) else ctx.invoked_target + if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: channel = await ctx.guild.fetch_channel(modlog.value) From 96ee4edf4834881482c44ec54a3da57cff6c04c7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 10:09:17 -0600 Subject: [PATCH 297/365] Fix context accessing to use invoked_name --- jarvis/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 667b80c..05a1016 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -89,7 +89,7 @@ class Jarvis(Snake): self.logger.debug(f"{add} additions, {sub} removals") async def _prerun(self, ctx: Context, *args, **kwargs) -> None: - name = ctx.invoked_name + name = ctx.invoke_target if isinstance(ctx, InteractionContext) and ctx.target_id: kwargs["context target"] = ctx.target args = " ".join(f"{k}:{v}" for k, v in kwargs.items()) @@ -131,7 +131,7 @@ class Jarvis(Snake): self, ctx: Context, error: Exception, *args: list, **kwargs: dict ) -> None: """NAFF on_command_error override.""" - name = ctx.invoked_name if isinstance(ctx, InteractionContext) else ctx.invoked_target + name = ctx.invoke_target self.logger.debug(f"Handling error in {name}: {error}") if isinstance(error, CommandOnCooldown): await ctx.send(str(error), ephemeral=True) @@ -201,7 +201,7 @@ class Jarvis(Snake): # Modlog async def on_command(self, ctx: Context) -> None: """NAFF on_command override.""" - name = ctx.invoked_name if isinstance(ctx, InteractionContext) else ctx.invoked_target + name = ctx.invoke_target if not isinstance(ctx.channel, DMChannel) and name not in ["pw"]: modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="activitylog")) if modlog: @@ -225,7 +225,7 @@ class Jarvis(Snake): fields = [ EmbedField( name="Command", - value=f"```ansi\n{CMD_FMT}{ctx.invoked_name}{RESET} {args}\n```", + value=f"```ansi\n{CMD_FMT}{ctx.invoke_target}{RESET} {args}\n```", inline=False, ), ] From c4f5234323a4389b467e7272e71a11e3936668a7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 10:11:47 -0600 Subject: [PATCH 298/365] Update poetry.lock --- poetry.lock | 71 +++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/poetry.lock b/poetry.lock index 937bf8b..e59cb47 100644 --- a/poetry.lock +++ b/poetry.lock @@ -388,17 +388,6 @@ docs = ["sphinx (==4.4.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", " lint = ["mypy (==0.940)", "flake8 (==4.0.1)", "flake8-bugbear (==22.1.11)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] -[[package]] -name = "molter" -version = "0.11.0" -description = "Shedding a new skin on Dis-Snek's commands." -category = "main" -optional = false -python-versions = ">=3.10" - -[package.dependencies] -dis-snek = ">=8.0.0" - [[package]] name = "mongoengine" version = "0.23.1" @@ -508,11 +497,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -608,7 +597,7 @@ six = ">=1.5" [[package]] name = "python-gitlab" -version = "3.3.0" +version = "3.4.0" description = "Interact with GitLab API" category = "main" optional = false @@ -718,7 +707,7 @@ jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "rook" -version = "0.1.170" +version = "0.1.171" description = "Rook is a Python package for on the fly debugging and data extraction for application in production" category = "main" optional = false @@ -912,7 +901,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "f04633ce0eaf27dfdf39ae2cc8cbbf6116c9b4aab91b3e58f5ae6dbcd560eb50" +content-hash = "3bfe48a36c3bc4bef6e6840eaeb51b01ffd6d038135451e45307eb843df981e0" [metadata.files] aiofile = [ @@ -1176,10 +1165,6 @@ marshmallow = [ {file = "marshmallow-3.15.0-py3-none-any.whl", hash = "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f"}, {file = "marshmallow-3.15.0.tar.gz", hash = "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5"}, ] -molter = [ - {file = "molter-0.11.0-py3-none-any.whl", hash = "sha256:4ae311e34fc93bfa37643f86c382b1f104753e451e9904995f0f34f5edda8daa"}, - {file = "molter-0.11.0.tar.gz", hash = "sha256:1e06e021a00986b9218e67bce062cb52eab5c86e8187b28e68f7dca8df853aaa"}, -] mongoengine = [ {file = "mongoengine-0.23.1-py3-none-any.whl", hash = "sha256:3d1c8b9f5d43144bd726a3f01e58d2831c6fb112960a4a60b3a26fa85e026ab3"}, {file = "mongoengine-0.23.1.tar.gz", hash = "sha256:de275e70cd58891dc46eef43369c522ce450dccb6d6f1979cbc9b93e6bdaf6cb"}, @@ -1584,8 +1569,8 @@ python-dateutil = [ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-gitlab = [ - {file = "python-gitlab-3.3.0.tar.gz", hash = "sha256:fef25d41a62f91da82ee20f72a728b9c69eef34cf0a3005cdbb9a0b471d5b498"}, - {file = "python_gitlab-3.3.0-py3-none-any.whl", hash = "sha256:ab1fd4c98a206f22f01f832bc58f24a09952089b7bbf67cdaee6308e7797503f"}, + {file = "python-gitlab-3.4.0.tar.gz", hash = "sha256:6180b81ee2f265ad8d8412956a1740b4d3ceca7b28ae2f707dfe62375fed0082"}, + {file = "python_gitlab-3.4.0-py3-none-any.whl", hash = "sha256:251b63f0589d51f854516948c84e9eb8df26e1e9dea595cf86b43f17c43007dd"}, ] pytz = [ {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, @@ -1723,26 +1708,26 @@ rich = [ {file = "rich-12.3.0.tar.gz", hash = "sha256:7e8700cda776337036a712ff0495b04052fb5f957c7dfb8df997f88350044b64"}, ] rook = [ - {file = "rook-0.1.170-cp27-cp27m-macosx_10_11_x86_64.whl", hash = "sha256:e2f90a44265606512ae434c44abe56b178dd5a5771016e3512b4f98598089576"}, - {file = "rook-0.1.170-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f4976bef70ec82f537668cb100d5510e9557e7edeba4aec46d9b354822e76cf1"}, - {file = "rook-0.1.170-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9228c7dc5cdd4235f258d33b5b55e1ddd2c1ac367f5326809698dc610242b88d"}, - {file = "rook-0.1.170-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:5977c819add5b648e5a0605337813d7add130a400d7eff41722c0c06ad27173e"}, - {file = "rook-0.1.170-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b0c81a76dfa98079e246ef54bdeb3d0bbbddef12c4a6b47a89e24386f6796589"}, - {file = "rook-0.1.170-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:ce97a8784a988f2dc9b9cc996574596587f374e1af77b7b6c8cc50d17f63c4d3"}, - {file = "rook-0.1.170-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa9a98871280168dbdfd3e1c4b6acf720932e7e449a8387314c49337008edbcc"}, - {file = "rook-0.1.170-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:a7ccdaeb4963786981770223117f918ea107f8d9e2b77324f7f9936afe0d1af5"}, - {file = "rook-0.1.170-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:152392d0243571620847e3267b11e7e50bea4f99e83ba94fef9ab0a828a418fb"}, - {file = "rook-0.1.170-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:048a0848c38de53dae6c06cb44c9a09b644716de6ecc28a3c96f462aefd976f8"}, - {file = "rook-0.1.170-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:58de02b2859458767963fc1641875a57003880d58f9eda0840ec96907541f70e"}, - {file = "rook-0.1.170-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:79f139e3397d5da35f951802a9fc800907df4c2fd82796a89c2841f6808aae14"}, - {file = "rook-0.1.170-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93628041f9f3b7a56af8eef8d42ca573095a988080d68f90637ee241e711d055"}, - {file = "rook-0.1.170-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:aabcff3f14bcb9674cb0fb23dd216dd64da532968142ae2bfc7c20a87be5540a"}, - {file = "rook-0.1.170-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:465151f1db95dea7004d47472c9a2ff751ba428cf634a2dd4846d0577979e837"}, - {file = "rook-0.1.170-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:259685e03ba8d2e60238ec3f45acffc45141989bb1c1de7f40ba791516e5d5d1"}, - {file = "rook-0.1.170-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:dfd180c89a1eee8525c10ffe658b20ff09cc9cd9a190889ffe735efe82d7c7e7"}, - {file = "rook-0.1.170-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dc123849965a11420324e89ccef26b015b7003f33cab5e5e6ce00450f7946fd"}, - {file = "rook-0.1.170-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3952e0ad674eaa26ab80db503b9890fc48f96e97a4ec64cd6a9cd3c04cd1e693"}, - {file = "rook-0.1.170.tar.gz", hash = "sha256:eeb9d98651822a47ccb9f9ec1a5cbce92d38970d24c8e5ce2f34908d73aa9f2a"}, + {file = "rook-0.1.171-cp27-cp27m-macosx_10_11_x86_64.whl", hash = "sha256:290ee068d18992fa5c27ebdb5c8745853c682cb44f26bedf858d323832ec8b74"}, + {file = "rook-0.1.171-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:96bd8983ad478f50ca22c524ec20fe095945f85c027b4d316ba46e844976bc35"}, + {file = "rook-0.1.171-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f36b614a85325cfd41e8e4a75ae29e4d7b91aac5981c0c1c4c36452e183e234e"}, + {file = "rook-0.1.171-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:b2d530a77fa1ebb59b15f7efbe810101fdbf7a10851a355c898ddedbfdafb513"}, + {file = "rook-0.1.171-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a8f4a998c5e8c03dc5a844b9831d916d8e7b79e704600f8d91b2886ef0fe62a"}, + {file = "rook-0.1.171-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:01fa10624a6c773ba8b71d9cd397fd37425ca484f0e64f15a9ee6b3214351cdb"}, + {file = "rook-0.1.171-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91db652819b6f6f99a5789bbd840cc46036a54908202492926ff62fbbaf9dcc5"}, + {file = "rook-0.1.171-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:d3770f3cc4626e56718d7c998024ca6cc75e240b82269b924b488e3d3f73e20c"}, + {file = "rook-0.1.171-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c7e20ecb27ceec33e4ca4efa4c77664d13ff47f64d176eabf81d97a481c8ed4"}, + {file = "rook-0.1.171-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aacdb2aea2ca559ce3d93eddea9da70d12b10d8b710c049a52781d88dc2e3b6f"}, + {file = "rook-0.1.171-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:6add21f04e3c28242638483cf34830fc931c61aac4bf47a88197f04273835e1f"}, + {file = "rook-0.1.171-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f8d478bda592fcc20fb73334fa24630cf2467c1faca81a1004908fc581d987b3"}, + {file = "rook-0.1.171-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a30e32da91b29629fc279990cdb952d6a7cd4945fbafafd7ad88fd71cf9fa624"}, + {file = "rook-0.1.171-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:d87f98c6f059571c40bcb395480cb10a33872e96c1520e8ea98d4703003c148c"}, + {file = "rook-0.1.171-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4474484145b6b4676fc51634dd979376caea7b5722694ec38132ba0e545c85ae"}, + {file = "rook-0.1.171-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:332f572df6a96f6e33e07d0354dfabade1d46a0aeb1aa54e25841bc1fd4239d2"}, + {file = "rook-0.1.171-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:b56ee99de3598a4dc76b25a66b09967f45bbab9c9dec3b6c91b03d4eed1cad7a"}, + {file = "rook-0.1.171-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6bc2dc7a329c7bcac29cae88ed486aba8452437456ee515b3e0ffcb1a1c8dfc9"}, + {file = "rook-0.1.171-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e9b96ab66de7616f6e65ad3f30f2bd442ee966a639faef3afcc740c16ed6c00"}, + {file = "rook-0.1.171.tar.gz", hash = "sha256:3ede95c8461546fd0baac2618397458ab7ddbbcb3f56e925fe21a871c70376c9"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, From 203d25c0c3cb85b9e78a6d9a10b8481681f9fdd8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 11:05:03 -0600 Subject: [PATCH 299/365] Add check for gl issue embed color --- jarvis/cogs/gl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 7fa80b2..559e03f 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -70,7 +70,9 @@ class GitlabCog(Scale): EmbedField(name="Assignee", value=assignee), EmbedField(name="Labels", value=labels), ] - color = self.project.labels.get(issue.labels[0]).color + color = "#FC6D27" + if labels: + color = self.project.labels.get(issue.labels[0]).color 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( From d1828513a2f82961f1c69357f505d4bf730e1dac Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 11:08:52 -0600 Subject: [PATCH 300/365] Use str.title instead of string indexes --- jarvis/cogs/gl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 559e03f..f72d914 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -66,7 +66,7 @@ class GitlabCog(Scale): labels = "None" fields = [ - EmbedField(name="State", value=issue.state[0].upper() + issue.state[1:]), + EmbedField(name="State", value=issue.state.title()), EmbedField(name="Assignee", value=assignee), EmbedField(name="Labels", value=labels), ] From fff9f1a3b5cf299ecaebf454eb8d0d746875639c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 11:15:11 -0600 Subject: [PATCH 301/365] Fix labels check --- jarvis/cogs/gl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index f72d914..662517e 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -62,7 +62,7 @@ class GitlabCog(Scale): labels = issue.labels if labels: labels = "\n".join(issue.labels) - if not labels: + else: labels = "None" fields = [ @@ -71,7 +71,7 @@ class GitlabCog(Scale): EmbedField(name="Labels", value=labels), ] color = "#FC6D27" - if labels: + if issue.labels: color = self.project.labels.get(issue.labels[0]).color fields.append(EmbedField(name="Created At", value=created_at)) if issue.state == "closed": From 11385ad675d66af8804db7098cae2a466914d1b8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 11:53:24 -0600 Subject: [PATCH 302/365] Add temprole, closes #128 --- jarvis/cogs/temprole.py | 120 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 jarvis/cogs/temprole.py diff --git a/jarvis/cogs/temprole.py b/jarvis/cogs/temprole.py new file mode 100644 index 0000000..e10edd8 --- /dev/null +++ b/jarvis/cogs/temprole.py @@ -0,0 +1,120 @@ +"""JARVIS temporary role handler.""" +import logging +from datetime import datetime, timezone + +from dateparser import parse +from dateparser_data.settings import default_parsers +from dis_snek import InteractionContext, Permissions, Scale, Snake +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_core.db.models import Temprole + +from jarvis.utils import build_embed +from jarvis.utils.permissions import admin_or_permissions + + +class TemproleCog(Scale): + """JARVIS Temporary Role Cog.""" + + def __init__(self, bot: Snake): + self.bot = bot + self.logger = logging.getLogger(__name__) + + @slash_command(name="temprole", description="Give a user a temporary role") + @slash_option( + name="user", description="User to grant role", opt_type=OptionTypes.USER, required=True + ) + @slash_option( + name="role", description="Role to grant", opt_type=OptionTypes.ROLE, required=True + ) + @slash_option( + name="duration", + description="Duration of temp role (i.e. 2 hours)", + opt_type=OptionTypes.STRING, + required=True, + ) + @slash_option( + name="reason", + description="Reason for temporary role", + opt_type=OptionTypes.STRING, + required=False, + ) + @check(admin_or_permissions(Permissions.MANAGE_ROLES)) + async def _temprole( + self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None + ) -> None: + if not isinstance(user, Member): + await ctx.send("User not in guild", ephemeral=True) + return + + if role.id == ctx.guild.id: + await ctx.send("Cannot add `@everyone` to users", ephemeral=True) + return + + if role.bot_managed or not role.is_assignable: + await ctx.send( + "Cannot assign this role, try lowering it below my role or using a different role", + ephemeral=True, + ) + return + + base_settings = { + "PREFER_DATES_FROM": "future", + "TIMEZONE": "UTC", + "RETURN_AS_TIMEZONE_AWARE": True, + } + rt_settings = base_settings.copy() + rt_settings["PARSERS"] = [ + x for x in default_parsers if x not in ["absolute-time", "timestamp"] + ] + + rt_duration = parse(duration, settings=rt_settings) + + at_settings = base_settings.copy() + at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] + at_duration = parse(duration, settings=at_settings) + + if rt_duration: + duration = rt_duration + elif at_duration: + duration = at_duration + else: + self.logger.debug(f"Failed to parse duration: {duration}") + await ctx.send(f"`{duration}` is not a parsable date, please try again", ephemeral=True) + return + + if duration < datetime.now(tz=timezone.utc): + await ctx.send( + f"`{duration}` is in the past. Past durations aren't allowed", ephemeral=True + ) + return + + await user.add_role(role, reason=reason) + await Temprole( + guild=ctx.guild.id, user=user.id, role=role.id, admin=ctx.author.id, expires_at=duration + ).commit() + + ts = int(duration.timestamp()) + + fields = ( + EmbedField(name="Role", value=role.mention), + EmbedField(name="Valid Until", value=f" ()"), + ) + + embed = build_embed( + title="Role granted", + description=f"Role temporarily granted to {user.mention}", + fields=fields, + ) + embed.set_author( + name=f"{user.username}#{user.discriminator}", icon_url=user.display_avatar.url + ) + + await ctx.send(embed=embed) From ee804dcf97773acbcb02d8ac89128b907d4712a4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 12:15:22 -0600 Subject: [PATCH 303/365] Add missing setup --- jarvis/cogs/temprole.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jarvis/cogs/temprole.py b/jarvis/cogs/temprole.py index e10edd8..ba657f1 100644 --- a/jarvis/cogs/temprole.py +++ b/jarvis/cogs/temprole.py @@ -118,3 +118,8 @@ class TemproleCog(Scale): ) await ctx.send(embed=embed) + + +def setup(bot: Snake) -> None: + """Add TemproleCog to JARVIS""" + TemproleCog(bot) From be37413f1dd9697e254266b8b7076eb925c76a98 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 12:15:48 -0600 Subject: [PATCH 304/365] Update poetry.lock --- poetry.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index e59cb47..9100b39 100644 --- a/poetry.lock +++ b/poetry.lock @@ -321,7 +321,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.8.5" +version = "0.9.0" description = "JARVIS core" category = "main" optional = false @@ -341,7 +341,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "457c7c060dc666d85d713259264bff4646dd813c" +resolved_reference = "29f90a8dc4e062d2d59f671b4cef2a61009dfde0" [[package]] name = "jinxed" From 82a8316a15f637b81d43f0e942d7225da2f0855b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 13:35:30 -0600 Subject: [PATCH 305/365] Add defers to a few longer commands --- jarvis/cogs/reddit.py | 2 ++ jarvis/cogs/temprole.py | 1 + 2 files changed, 3 insertions(+) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index eb148f7..cc51246 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -235,6 +235,7 @@ class RedditCog(Scale): name="name", description="Subreddit name", opt_type=OptionTypes.STRING, required=True ) async def _subreddit_hot(self, ctx: InteractionContext, name: str) -> None: + await ctx.defer() name = name.replace("r/", "") if len(name) > 20 or len(name) < 3: await ctx.send("Invalid Subreddit name", ephemeral=True) @@ -275,6 +276,7 @@ class RedditCog(Scale): ], ) async def _subreddit_top(self, ctx: InteractionContext, name: str, time: str = "all") -> None: + await ctx.defer() name = name.replace("r/", "") if len(name) > 20 or len(name) < 3: await ctx.send("Invalid Subreddit name", ephemeral=True) diff --git a/jarvis/cogs/temprole.py b/jarvis/cogs/temprole.py index ba657f1..a9d6f6f 100644 --- a/jarvis/cogs/temprole.py +++ b/jarvis/cogs/temprole.py @@ -50,6 +50,7 @@ class TemproleCog(Scale): async def _temprole( self, ctx: InteractionContext, user: Member, role: Role, duration: str, reason: str = None ) -> None: + await ctx.defer() if not isinstance(user, Member): await ctx.send("User not in guild", ephemeral=True) return From abbb02f07ae712cc069a33407c5fe19581d8d7ec Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 13:41:48 -0600 Subject: [PATCH 306/365] Add nsfw filter on message sending --- jarvis/cogs/reddit.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index cc51246..934d804 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -255,7 +255,8 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - await ctx.send(embeds=embeds) + ephemeral = post.over_18 and not ctx.channel.nsfw + await ctx.send(embeds=embeds, ephemeral=ephemeral) @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the top post of a subreddit") @slash_option( @@ -325,7 +326,8 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - await ctx.send(embeds=embeds) + ephemeral = post.over_18 and not ctx.channel.nsfw + await ctx.send(embeds=embeds, ephemeral=ephemeral) @reddit.subcommand( sub_cmd_name="rising", sub_cmd_description="Get a rising post of a subreddit" @@ -354,7 +356,8 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - await ctx.send(embeds=embeds) + ephemeral = post.over_18 and not ctx.channel.nsfw + await ctx.send(embeds=embeds, ephemeral=ephemeral) def setup(bot: Snake) -> None: From 602cea9058a8796bebc6700db7534698a8619728 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 13:58:19 -0600 Subject: [PATCH 307/365] Change nsfw filtering --- jarvis/cogs/reddit.py | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 934d804..01de656 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -255,8 +255,14 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - ephemeral = post.over_18 and not ctx.channel.nsfw - await ctx.send(embeds=embeds, ephemeral=ephemeral) + if post.over_18 and not ctx.channel.nsfw: + try: + await ctx.author.send(embeds=embeds) + await ctx.send("Hey! Due to content, I had to DM the result to you") + except Exception: + await ctx.send("Hey! Due to content, I cannot share the result") + else: + await ctx.send(embeds=embeds) @reddit.subcommand(sub_cmd_name="top", sub_cmd_description="Get the top post of a subreddit") @slash_option( @@ -297,7 +303,14 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - await ctx.send(embeds=embeds) + if post.over_18 and not ctx.channel.nsfw: + try: + await ctx.author.send(embeds=embeds) + await ctx.send("Hey! Due to content, I had to DM the result to you") + except Exception: + await ctx.send("Hey! Due to content, I cannot share the result") + else: + await ctx.send(embeds=embeds) @reddit.subcommand( sub_cmd_name="random", sub_cmd_description="Get a random post of a subreddit" @@ -326,8 +339,14 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - ephemeral = post.over_18 and not ctx.channel.nsfw - await ctx.send(embeds=embeds, ephemeral=ephemeral) + if post.over_18 and not ctx.channel.nsfw: + try: + await ctx.author.send(embeds=embeds) + await ctx.send("Hey! Due to content, I had to DM the result to you") + except Exception: + await ctx.send("Hey! Due to content, I cannot share the result") + else: + await ctx.send(embeds=embeds) @reddit.subcommand( sub_cmd_name="rising", sub_cmd_description="Get a rising post of a subreddit" @@ -356,8 +375,14 @@ class RedditCog(Scale): return embeds = await self.post_embeds(subreddit, post) - ephemeral = post.over_18 and not ctx.channel.nsfw - await ctx.send(embeds=embeds, ephemeral=ephemeral) + if post.over_18 and not ctx.channel.nsfw: + try: + await ctx.author.send(embeds=embeds) + await ctx.send("Hey! Due to content, I had to DM the result to you") + except Exception: + await ctx.send("Hey! Due to content, I cannot share the result") + else: + await ctx.send(embeds=embeds) def setup(bot: Snake) -> None: From 8e07dceed727bad4d6e736e6c758ceefaa78a65c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Mon, 2 May 2022 14:39:46 -0600 Subject: [PATCH 308/365] Anaffer migration (dis-snek -> naff rename) --- jarvis/__init__.py | 2 +- jarvis/client.py | 32 ++++++++++++++++---------------- jarvis/cogs/admin/__init__.py | 4 ++-- jarvis/cogs/admin/ban.py | 18 +++++++++--------- jarvis/cogs/admin/kick.py | 12 ++++++------ jarvis/cogs/admin/lock.py | 20 ++++++++++---------- jarvis/cogs/admin/lockdown.py | 32 +++++++++++++++++--------------- jarvis/cogs/admin/mute.py | 16 ++++++++-------- jarvis/cogs/admin/purge.py | 16 ++++++++-------- jarvis/cogs/admin/roleping.py | 24 ++++++++++++------------ jarvis/cogs/admin/warning.py | 18 +++++++++--------- jarvis/cogs/autoreact.py | 20 ++++++++++---------- jarvis/cogs/botutil.py | 14 +++++++------- jarvis/cogs/ctc2.py | 26 +++++++++++++------------- jarvis/cogs/dbrand.py | 16 ++++++++-------- jarvis/cogs/dev.py | 24 ++++++++++++------------ jarvis/cogs/gl.py | 22 +++++++++++----------- jarvis/cogs/image.py | 18 +++++++++--------- jarvis/cogs/reddit.py | 24 ++++++++++++------------ jarvis/cogs/remindme.py | 24 ++++++++++++------------ jarvis/cogs/rolegiver.py | 26 +++++++++++++------------- jarvis/cogs/settings.py | 26 +++++++++++++------------- jarvis/cogs/starboard.py | 22 +++++++++++----------- jarvis/cogs/temprole.py | 20 ++++++++++---------- jarvis/cogs/twitter.py | 22 +++++++++++----------- jarvis/cogs/util.py | 28 ++++++++++++++-------------- jarvis/cogs/verify.py | 16 ++++++++-------- jarvis/config.py | 2 +- jarvis/utils/__init__.py | 6 +++--- jarvis/utils/cogs.py | 10 +++++----- jarvis/utils/embeds.py | 4 ++-- jarvis/utils/permissions.py | 2 +- jarvis/utils/updates.py | 24 ++++++++++++------------ 33 files changed, 296 insertions(+), 294 deletions(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index be4817f..8a17dd0 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -3,9 +3,9 @@ import logging import jurigged import rook -from dis_snek import Intents from jarvis_core.db import connect from jarvis_core.log import get_logger +from naff import Intents from jarvis import const from jarvis.client import Jarvis diff --git a/jarvis/client.py b/jarvis/client.py index 05a1016..f5275b7 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -5,27 +5,27 @@ import traceback from datetime import datetime, timedelta, timezone from aiohttp import ClientSession -from dis_snek import Snake, listen -from dis_snek.api.events.discord import ( +from jarvis_core.db import q +from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning +from jarvis_core.filters import invites, url +from jarvis_core.util.ansi import RESET, Fore, Format, fmt +from naff import Client, listen +from naff.api.events.discord import ( MemberAdd, MemberRemove, MessageCreate, MessageDelete, MessageUpdate, ) -from dis_snek.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException -from dis_snek.client.utils.misc_utils import find_all -from dis_snek.models.discord.channel import DMChannel -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.enums import Permissions -from dis_snek.models.discord.message import Message -from dis_snek.models.snek.context import Context, InteractionContext, PrefixedContext -from dis_snek.models.snek.tasks.task import Task -from dis_snek.models.snek.tasks.triggers import IntervalTrigger -from jarvis_core.db import q -from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning -from jarvis_core.filters import invites, url -from jarvis_core.util.ansi import RESET, Fore, Format, fmt +from naff.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException +from naff.client.utils.misc_utils import find_all +from naff.models.discord.channel import DMChannel +from naff.models.discord.embed import EmbedField +from naff.models.discord.enums import Permissions +from naff.models.discord.message import Message +from naff.models.naff.context import Context, InteractionContext, PrefixedContext +from naff.models.naff.tasks.task import Task +from naff.models.naff.tasks.triggers import IntervalTrigger from pastypy import AsyncPaste as Paste from jarvis import const @@ -55,7 +55,7 @@ VAL_FMT = fmt(Fore.WHITE) CMD_FMT = fmt(Fore.GREEN, Format.BOLD) -class Jarvis(Snake): +class Jarvis(Client): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 super().__init__(*args, **kwargs) self.logger = logging.getLogger(__name__) diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index f0fb1af..6e2a8f8 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -1,12 +1,12 @@ """JARVIS Admin Cogs.""" import logging -from dis_snek import Snake +from naff import Client from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add admin cogs to JARVIS""" logger = logging.getLogger(__name__) msg = "Loaded jarvis.cogs.admin.{}" diff --git a/jarvis/cogs/admin/ban.py b/jarvis/cogs/admin/ban.py index 43fcac1..2cbe0c6 100644 --- a/jarvis/cogs/admin/ban.py +++ b/jarvis/cogs/admin/ban.py @@ -1,21 +1,21 @@ """JARVIS BanCog.""" import re -from dis_snek import InteractionContext, Permissions -from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.ext.paginators import Paginator -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import User -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Ban, Unban +from naff import InteractionContext, Permissions +from naff.client.utils.misc_utils import find, find_all +from naff.ext.paginators import Paginator +from naff.models.discord.embed import EmbedField +from naff.models.discord.user import User +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, SlashCommandChoice, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Ban, Unban +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.cogs import ModcaseCog diff --git a/jarvis/cogs/admin/kick.py b/jarvis/cogs/admin/kick.py index e1684a8..820b777 100644 --- a/jarvis/cogs/admin/kick.py +++ b/jarvis/cogs/admin/kick.py @@ -1,14 +1,14 @@ """JARVIS KickCog.""" -from dis_snek import InteractionContext, Permissions -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import User -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db.models import Kick +from naff import InteractionContext, Permissions +from naff.models.discord.embed import EmbedField +from naff.models.discord.user import User +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db.models import Kick +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.cogs import ModcaseCog diff --git a/jarvis/cogs/admin/lock.py b/jarvis/cogs/admin/lock.py index fb18a8b..602959d 100644 --- a/jarvis/cogs/admin/lock.py +++ b/jarvis/cogs/admin/lock.py @@ -2,26 +2,26 @@ import logging from typing import Union -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import get -from dis_snek.models.discord.channel import GuildText, GuildVoice -from dis_snek.models.discord.enums import Permissions -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Permission +from naff import Client, Cog, InteractionContext +from naff.client.utils.misc_utils import get +from naff.models.discord.channel import GuildText, GuildVoice +from naff.models.discord.enums import Permissions +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Lock, Permission +from naff.models.naff.command import check from jarvis.utils.permissions import admin_or_permissions -class LockCog(Scale): +class LockCog(Cog): """JARVIS LockCog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) diff --git a/jarvis/cogs/admin/lockdown.py b/jarvis/cogs/admin/lockdown.py index 8ee7ac5..d06b3e3 100644 --- a/jarvis/cogs/admin/lockdown.py +++ b/jarvis/cogs/admin/lockdown.py @@ -1,25 +1,27 @@ """JARVIS LockdownCog.""" import logging -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import find_all, get -from dis_snek.models.discord.channel import GuildCategory, GuildChannel -from dis_snek.models.discord.enums import Permissions -from dis_snek.models.discord.guild import Guild -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Lock, Lockdown, Permission +from naff import Client, Cog, InteractionContext +from naff.client.utils.misc_utils import find_all, get +from naff.models.discord.channel import GuildCategory, GuildChannel +from naff.models.discord.enums import Permissions +from naff.models.discord.guild import Guild +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Lock, Lockdown, Permission +from naff.models.naff.command import check from jarvis.utils.permissions import admin_or_permissions -async def lock(bot: Snake, target: GuildChannel, admin: Member, reason: str, duration: int) -> None: +async def lock( + bot: Client, target: GuildChannel, admin: Member, reason: str, duration: int +) -> None: """ Lock an existing channel @@ -44,7 +46,7 @@ async def lock(bot: Snake, target: GuildChannel, admin: Member, reason: str, dur ).commit() -async def lock_all(bot: Snake, guild: Guild, admin: Member, reason: str, duration: int) -> None: +async def lock_all(bot: Client, guild: Guild, admin: Member, reason: str, duration: int) -> None: """ Lock all channels @@ -64,7 +66,7 @@ async def lock_all(bot: Snake, guild: Guild, admin: Member, reason: str, duratio await lock(bot, channel, admin, reason, duration) -async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: +async def unlock_all(bot: Client, guild: Guild, admin: Member) -> None: """ Unlock all locked channels @@ -92,10 +94,10 @@ async def unlock_all(bot: Snake, guild: Guild, admin: Member) -> None: await lockdown.commit() -class LockdownCog(Scale): +class LockdownCog(Cog): """JARVIS LockdownCog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) diff --git a/jarvis/cogs/admin/mute.py b/jarvis/cogs/admin/mute.py index a746e12..cbcfab3 100644 --- a/jarvis/cogs/admin/mute.py +++ b/jarvis/cogs/admin/mute.py @@ -4,12 +4,13 @@ from datetime import datetime, timedelta, timezone from dateparser import parse from dateparser_data.settings import default_parsers -from dis_snek import InteractionContext, Permissions -from dis_snek.client.errors import Forbidden -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.modal import InputText, Modal, TextStyles -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db.models import Mute +from naff import InteractionContext, Permissions +from naff.client.errors import Forbidden +from naff.models.discord.embed import EmbedField +from naff.models.discord.modal import InputText, Modal, TextStyles +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( CommandTypes, OptionTypes, SlashCommandChoice, @@ -17,8 +18,7 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db.models import Mute +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.cogs import ModcaseCog diff --git a/jarvis/cogs/admin/purge.py b/jarvis/cogs/admin/purge.py index 4036eb9..abcb588 100644 --- a/jarvis/cogs/admin/purge.py +++ b/jarvis/cogs/admin/purge.py @@ -1,24 +1,24 @@ """JARVIS PurgeCog.""" import logging -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.models.discord.channel import GuildText -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Autopurge, Purge +from naff import Client, Cog, InteractionContext, Permissions +from naff.models.discord.channel import GuildText +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Autopurge, Purge +from naff.models.naff.command import check from jarvis.utils.permissions import admin_or_permissions -class PurgeCog(Scale): +class PurgeCog(Cog): """JARVIS PurgeCog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) diff --git a/jarvis/cogs/admin/roleping.py b/jarvis/cogs/admin/roleping.py index 6fda235..3bbe1da 100644 --- a/jarvis/cogs/admin/roleping.py +++ b/jarvis/cogs/admin/roleping.py @@ -1,29 +1,29 @@ """JARVIS RolepingCog.""" import logging -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import find_all -from dis_snek.ext.paginators import Paginator -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.role import Role -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Roleping +from naff import Client, Cog, InteractionContext, Permissions +from naff.client.utils.misc_utils import find_all +from naff.ext.paginators import Paginator +from naff.models.discord.embed import EmbedField +from naff.models.discord.role import Role +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Roleping +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions -class RolepingCog(Scale): +class RolepingCog(Cog): """JARVIS RolepingCog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) diff --git a/jarvis/cogs/admin/warning.py b/jarvis/cogs/admin/warning.py index de04ac2..c118ee0 100644 --- a/jarvis/cogs/admin/warning.py +++ b/jarvis/cogs/admin/warning.py @@ -1,19 +1,19 @@ """JARVIS WarningCog.""" from datetime import datetime, timedelta, timezone -from dis_snek import InteractionContext, Permissions -from dis_snek.client.utils.misc_utils import get_all -from dis_snek.ext.paginators import Paginator -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Warning +from naff import InteractionContext, Permissions +from naff.client.utils.misc_utils import get_all +from naff.ext.paginators import Paginator +from naff.models.discord.embed import EmbedField +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Warning +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.cogs import ModcaseCog diff --git a/jarvis/cogs/autoreact.py b/jarvis/cogs/autoreact.py index db1a41f..751474d 100644 --- a/jarvis/cogs/autoreact.py +++ b/jarvis/cogs/autoreact.py @@ -3,26 +3,26 @@ import logging import re from typing import Optional, Tuple -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import find -from dis_snek.models.discord.channel import GuildText -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Autoreact +from naff import Client, Cog, InteractionContext, Permissions +from naff.client.utils.misc_utils import find +from naff.models.discord.channel import GuildText +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Autoreact +from naff.models.naff.command import check from jarvis.data.unicode import emoji_list from jarvis.utils.permissions import admin_or_permissions -class AutoReactCog(Scale): +class AutoReactCog(Cog): """JARVIS Autoreact Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) self.custom_emote = re.compile(r"^<:\w+:(\d+)>$") @@ -206,6 +206,6 @@ class AutoReactCog(Scale): await ctx.send(message) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add AutoReactCog to JARVIS""" AutoReactCog(bot) diff --git a/jarvis/cogs/botutil.py b/jarvis/cogs/botutil.py index 8baff9a..aaeb377 100644 --- a/jarvis/cogs/botutil.py +++ b/jarvis/cogs/botutil.py @@ -5,22 +5,22 @@ from io import BytesIO import psutil from aiofile import AIOFile, LineReader -from dis_snek import PrefixedContext, Scale, Snake, prefixed_command -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.file import File +from naff import Client, Cog, PrefixedContext, prefixed_command +from naff.models.discord.embed import EmbedField +from naff.models.discord.file import File from rich.console import Console from jarvis.utils import build_embed from jarvis.utils.updates import update -class BotutilCog(Scale): +class BotutilCog(Cog): """JARVIS Bot Utility Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) - self.add_scale_check(self.is_owner) + self.add_cog_check(self.is_owner) async def is_owner(self, ctx: PrefixedContext) -> bool: """Checks if author is bot owner.""" @@ -115,6 +115,6 @@ class BotutilCog(Scale): await ctx.reply(embed=embed) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add BotutilCog to JARVIS""" BotutilCog(bot) diff --git a/jarvis/cogs/ctc2.py b/jarvis/cogs/ctc2.py index f1de4c1..2a20c0f 100644 --- a/jarvis/cogs/ctc2.py +++ b/jarvis/cogs/ctc2.py @@ -3,20 +3,20 @@ import logging import re import aiohttp -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.ext.paginators import Paginator -from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.user import Member, User -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Guess +from naff import Client, Cog, InteractionContext +from naff.ext.paginators import Paginator +from naff.models.discord.components import ActionRow, Button, ButtonStyles +from naff.models.discord.embed import EmbedField +from naff.models.discord.user import Member, User +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets -from jarvis_core.db import q -from jarvis_core.db.models import Guess +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets from jarvis.utils import build_embed @@ -29,10 +29,10 @@ invites = re.compile( ) -class CTCCog(Scale): +class CTCCog(Cog): """JARVIS Complete the Code 2 Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() @@ -146,6 +146,6 @@ class CTCCog(Scale): await paginator.send(ctx) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add CTCCog to JARVIS""" CTCCog(bot) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 8fe1c98..9000e80 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -3,15 +3,15 @@ import logging import re import aiohttp -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.snek.application_commands import ( +from naff import Client, Cog, InteractionContext +from naff.models.discord.embed import EmbedField +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets from jarvis.config import get_config from jarvis.data.dbrand import shipping_lookup @@ -20,14 +20,14 @@ from jarvis.utils import build_embed guild_ids = [862402786116763668] # [578757004059738142, 520021794380447745, 862402786116763668] -class DbrandCog(Scale): +class DbrandCog(Cog): """ dbrand functions for JARVIS Mostly support functions. Credit @cpixl for the shipping API """ - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) self.base_url = "https://dbrand.com/" @@ -197,6 +197,6 @@ class DbrandCog(Scale): await ctx.send(embed=embed) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add dbrandcog to JARVIS""" DbrandCog(bot) diff --git a/jarvis/cogs/dev.py b/jarvis/cogs/dev.py index 06dc1ec..bc4cb42 100644 --- a/jarvis/cogs/dev.py +++ b/jarvis/cogs/dev.py @@ -8,20 +8,20 @@ import uuid as uuidpy import ulid as ulidpy from bson import ObjectId -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.message import Attachment -from dis_snek.models.snek.application_commands import ( +from jarvis_core.filters import invites, url +from jarvis_core.util import convert_bytesize, hash +from jarvis_core.util.http import get_size +from naff import Client, Cog, InteractionContext +from naff.models.discord.embed import EmbedField +from naff.models.discord.message import Attachment +from naff.models.naff.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_core.filters import invites, url -from jarvis_core.util import convert_bytesize, hash -from jarvis_core.util.http import get_size +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets from jarvis.utils import build_embed @@ -45,10 +45,10 @@ UUID_GET = {3: uuidpy.uuid3, 5: uuidpy.uuid5} MAX_FILESIZE = 5 * (1024**3) # 5GB -class DevCog(Scale): +class DevCog(Cog): """JARVIS Developer Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -275,6 +275,6 @@ class DevCog(Scale): await ctx.send(f"```haskell\n{output}\n```") -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add DevCog to JARVIS""" DevCog(bot) diff --git a/jarvis/cogs/gl.py b/jarvis/cogs/gl.py index 662517e..cde44af 100644 --- a/jarvis/cogs/gl.py +++ b/jarvis/cogs/gl.py @@ -4,20 +4,20 @@ import logging from datetime import datetime import gitlab -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.ext.paginators import Paginator -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.modal import InputText, Modal, TextStyles -from dis_snek.models.discord.user import Member -from dis_snek.models.snek.application_commands import ( +from naff import Client, Cog, InteractionContext +from naff.ext.paginators import Paginator +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.modal import InputText, Modal, TextStyles +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, SlashCommandChoice, slash_command, slash_option, ) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets from jarvis.config import JarvisConfig from jarvis.utils import build_embed @@ -25,10 +25,10 @@ from jarvis.utils import build_embed guild_ids = [862402786116763668] -class GitlabCog(Scale): +class GitlabCog(Cog): """JARVIS GitLab Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) config = JarvisConfig.from_yaml() @@ -465,7 +465,7 @@ class GitlabCog(Scale): await resp.send(embed=embed) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add GitlabCog to JARVIS if Gitlab token exists.""" if JarvisConfig.from_yaml().gitlab_token: GitlabCog(bot) diff --git a/jarvis/cogs/image.py b/jarvis/cogs/image.py index 775d191..f44c9a7 100644 --- a/jarvis/cogs/image.py +++ b/jarvis/cogs/image.py @@ -6,30 +6,30 @@ from io import BytesIO import aiohttp import cv2 import numpy as np -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.file import File -from dis_snek.models.discord.message import Attachment -from dis_snek.models.snek.application_commands import ( +from jarvis_core.util import convert_bytesize, unconvert_bytesize +from naff import Client, Cog, InteractionContext +from naff.models.discord.embed import EmbedField +from naff.models.discord.file import File +from naff.models.discord.message import Attachment +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from jarvis_core.util import convert_bytesize, unconvert_bytesize from jarvis.utils import build_embed MIN_ACCURACY = 0.80 -class ImageCog(Scale): +class ImageCog(Cog): """ Image processing functions for JARVIS May be categorized under util later """ - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) self._session = aiohttp.ClientSession() @@ -153,6 +153,6 @@ class ImageCog(Scale): ) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add ImageCog to JARVIS""" ImageCog(bot) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 01de656..1da3474 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -7,20 +7,20 @@ from asyncpraw import Reddit from asyncpraw.models.reddit.submission import Submission from asyncpraw.models.reddit.submission import Subreddit as Sub from asyncprawcore.exceptions import Forbidden, NotFound, Redirect -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import get -from dis_snek.models.discord.channel import ChannelTypes, GuildText -from dis_snek.models.discord.components import ActionRow, Select, SelectOption -from dis_snek.models.discord.embed import Embed -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Subreddit, SubredditFollow +from naff import Client, Cog, InteractionContext, Permissions +from naff.client.utils.misc_utils import get +from naff.models.discord.channel import ChannelTypes, GuildText +from naff.models.discord.components import ActionRow, Select, SelectOption +from naff.models.discord.embed import Embed +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, SlashCommandChoice, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Subreddit, SubredditFollow +from naff.models.naff.command import check from jarvis import const from jarvis.config import JarvisConfig @@ -30,10 +30,10 @@ from jarvis.utils.permissions import admin_or_permissions DEFAULT_USER_AGENT = f"python:JARVIS:{const.__version__} (by u/zevaryx)" -class RedditCog(Scale): +class RedditCog(Cog): """JARVIS Reddit Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) config = JarvisConfig.from_yaml() @@ -385,7 +385,7 @@ class RedditCog(Scale): await ctx.send(embeds=embeds) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add RedditCog to JARVIS""" if JarvisConfig.from_yaml().reddit: RedditCog(bot) diff --git a/jarvis/cogs/remindme.py b/jarvis/cogs/remindme.py index a3994b5..ef70d42 100644 --- a/jarvis/cogs/remindme.py +++ b/jarvis/cogs/remindme.py @@ -8,20 +8,20 @@ from typing import List from bson import ObjectId from dateparser import parse from dateparser_data.settings import default_parsers -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.client.utils.misc_utils import get -from dis_snek.models.discord.channel import GuildChannel -from dis_snek.models.discord.components import ActionRow, Select, SelectOption -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.modal import InputText, Modal, TextStyles -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Reminder +from naff import Client, Cog, InteractionContext +from naff.client.utils.misc_utils import get +from naff.models.discord.channel import GuildChannel +from naff.models.discord.components import ActionRow, Select, SelectOption +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.modal import InputText, Modal, TextStyles +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_command, slash_option, ) -from jarvis_core.db import q -from jarvis_core.db.models import Reminder from jarvis.utils import build_embed @@ -33,10 +33,10 @@ invites = re.compile( ) -class RemindmeCog(Scale): +class RemindmeCog(Cog): """JARVIS Remind Me Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -336,6 +336,6 @@ class RemindmeCog(Scale): self.logger.debug("Ignoring deletion error") -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add RemindmeCog to JARVIS""" RemindmeCog(bot) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index ec08eb8..01fcc8a 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -2,29 +2,29 @@ import asyncio import logging -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import get -from dis_snek.models.discord.components import ActionRow, Select, SelectOption -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.role import Role -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Rolegiver +from naff import Client, Cog, InteractionContext, Permissions +from naff.client.utils.misc_utils import get +from naff.models.discord.components import ActionRow, Select, SelectOption +from naff.models.discord.embed import EmbedField +from naff.models.discord.role import Role +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check, cooldown -from dis_snek.models.snek.cooldowns import Buckets -from jarvis_core.db import q -from jarvis_core.db.models import Rolegiver +from naff.models.naff.command import check, cooldown +from naff.models.naff.cooldowns import Buckets from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions -class RolegiverCog(Scale): +class RolegiverCog(Cog): """JARVIS Role Giver Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -385,6 +385,6 @@ class RolegiverCog(Scale): await ctx.send("Rolegiver cleanup finished") -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add RolegiverCog to JARVIS""" RolegiverCog(bot) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 39627fe..3e9c98c 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -3,29 +3,29 @@ import asyncio import logging from typing import Any -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.channel import GuildText -from dis_snek.models.discord.components import ActionRow, Button, ButtonStyles -from dis_snek.models.discord.embed import EmbedField -from dis_snek.models.discord.enums import Permissions -from dis_snek.models.discord.role import Role -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import Setting +from naff import Client, Cog, InteractionContext +from naff.models.discord.channel import GuildText +from naff.models.discord.components import ActionRow, Button, ButtonStyles +from naff.models.discord.embed import EmbedField +from naff.models.discord.enums import Permissions +from naff.models.discord.role import Role +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Setting +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions -class SettingsCog(Scale): +class SettingsCog(Cog): """JARVIS Settings Management Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -278,6 +278,6 @@ class SettingsCog(Scale): await message.edit(content="Guild settings not cleared", components=components) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add SettingsCog to JARVIS""" SettingsCog(bot) diff --git a/jarvis/cogs/starboard.py b/jarvis/cogs/starboard.py index 3ced678..a87267c 100644 --- a/jarvis/cogs/starboard.py +++ b/jarvis/cogs/starboard.py @@ -1,20 +1,20 @@ """JARVIS Starboard Cog.""" import logging -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 ( +from jarvis_core.db import q +from jarvis_core.db.models import Star, Starboard +from naff import Client, Cog, InteractionContext, Permissions +from naff.models.discord.channel import GuildText +from naff.models.discord.components import ActionRow, Select, SelectOption +from naff.models.discord.message import Message +from naff.models.naff.application_commands import ( CommandTypes, OptionTypes, SlashCommand, context_menu, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import Star, Starboard +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions @@ -28,10 +28,10 @@ supported_images = [ ] -class StarboardCog(Scale): +class StarboardCog(Cog): """JARVIS Starboard Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -318,6 +318,6 @@ class StarboardCog(Scale): await ctx.send(f"Star {id} deleted from {starboard.mention}") -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add StarboardCog to JARVIS""" StarboardCog(bot) diff --git a/jarvis/cogs/temprole.py b/jarvis/cogs/temprole.py index a9d6f6f..c2a443e 100644 --- a/jarvis/cogs/temprole.py +++ b/jarvis/cogs/temprole.py @@ -4,26 +4,26 @@ from datetime import datetime, timezone from dateparser import parse from dateparser_data.settings import default_parsers -from dis_snek import InteractionContext, Permissions, Scale, Snake -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 ( +from jarvis_core.db.models import Temprole +from naff import Client, Cog, InteractionContext, Permissions +from naff.models.discord.embed import EmbedField +from naff.models.discord.role import Role +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( OptionTypes, slash_command, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db.models import Temprole +from naff.models.naff.command import check from jarvis.utils import build_embed from jarvis.utils.permissions import admin_or_permissions -class TemproleCog(Scale): +class TemproleCog(Cog): """JARVIS Temporary Role Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -121,6 +121,6 @@ class TemproleCog(Scale): await ctx.send(embed=embed) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add TemproleCog to JARVIS""" TemproleCog(bot) diff --git a/jarvis/cogs/twitter.py b/jarvis/cogs/twitter.py index 733893d..16a650d 100644 --- a/jarvis/cogs/twitter.py +++ b/jarvis/cogs/twitter.py @@ -3,27 +3,27 @@ import asyncio import logging import tweepy -from dis_snek import InteractionContext, Permissions, Scale, Snake -from dis_snek.client.utils.misc_utils import get -from dis_snek.models.discord.channel import GuildText -from dis_snek.models.discord.components import ActionRow, Select, SelectOption -from dis_snek.models.snek.application_commands import ( +from jarvis_core.db import q +from jarvis_core.db.models import TwitterAccount, TwitterFollow +from naff import Client, Cog, InteractionContext, Permissions +from naff.client.utils.misc_utils import get +from naff.models.discord.channel import GuildText +from naff.models.discord.components import ActionRow, Select, SelectOption +from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, slash_option, ) -from dis_snek.models.snek.command import check -from jarvis_core.db import q -from jarvis_core.db.models import TwitterAccount, TwitterFollow +from naff.models.naff.command import check from jarvis.config import JarvisConfig from jarvis.utils.permissions import admin_or_permissions -class TwitterCog(Scale): +class TwitterCog(Cog): """JARVIS Twitter Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) config = JarvisConfig.from_yaml() @@ -243,7 +243,7 @@ class TwitterCog(Scale): await message.edit(components=components) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add TwitterCog to JARVIS""" if JarvisConfig.from_yaml().twitter: TwitterCog(bot) diff --git a/jarvis/cogs/util.py b/jarvis/cogs/util.py index af14ddb..e709479 100644 --- a/jarvis/cogs/util.py +++ b/jarvis/cogs/util.py @@ -8,14 +8,14 @@ from io import BytesIO import numpy as np from dateparser import parse -from dis_snek import InteractionContext, Scale, Snake, const -from dis_snek.models.discord.channel import GuildCategory, GuildText, GuildVoice -from dis_snek.models.discord.embed import EmbedField -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 Member, User -from dis_snek.models.snek.application_commands import ( +from naff import Client, Cog, InteractionContext, const +from naff.models.discord.channel import GuildCategory, GuildText, GuildVoice +from naff.models.discord.embed import EmbedField +from naff.models.discord.file import File +from naff.models.discord.guild import Guild +from naff.models.discord.role import Role +from naff.models.discord.user import Member, User +from naff.models.naff.application_commands import ( CommandTypes, OptionTypes, SlashCommandChoice, @@ -23,8 +23,8 @@ from dis_snek.models.snek.application_commands import ( slash_command, slash_option, ) -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets from PIL import Image from tzlocal import get_localzone @@ -36,14 +36,14 @@ from jarvis.utils import build_embed, get_repo_hash JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA") -class UtilCog(Scale): +class UtilCog(Cog): """ Utility functions for JARVIS Mostly system utility functions, but may change over time """ - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -57,7 +57,7 @@ class UtilCog(Scale): uptime = int(self.bot.start_time.timestamp()) fields.append(EmbedField(name="Version", value=jconst.__version__, inline=True)) - fields.append(EmbedField(name="dis-snek", value=const.__version__, inline=True)) + fields.append(EmbedField(name="naff", value=const.__version__, inline=True)) fields.append(EmbedField(name="Git Hash", value=get_repo_hash()[:7], inline=True)) fields.append(EmbedField(name="Online Since", value=f"", inline=False)) num_domains = len(self.bot.phishing_domains) @@ -375,6 +375,6 @@ class UtilCog(Scale): await ctx.send(embed=embed, ephemeral=private) -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add UtilCog to JARVIS""" UtilCog(bot) diff --git a/jarvis/cogs/verify.py b/jarvis/cogs/verify.py index dcd0b95..d921a60 100644 --- a/jarvis/cogs/verify.py +++ b/jarvis/cogs/verify.py @@ -3,13 +3,13 @@ import asyncio import logging from random import randint -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.components import Button, ButtonStyles, spread_to_rows -from dis_snek.models.snek.application_commands import slash_command -from dis_snek.models.snek.command import cooldown -from dis_snek.models.snek.cooldowns import Buckets from jarvis_core.db import q from jarvis_core.db.models import Setting +from naff import Client, Cog, InteractionContext +from naff.models.discord.components import Button, ButtonStyles, spread_to_rows +from naff.models.naff.application_commands import slash_command +from naff.models.naff.command import cooldown +from naff.models.naff.cooldowns import Buckets def create_layout() -> list: @@ -30,10 +30,10 @@ def create_layout() -> list: return spread_to_rows(*buttons, max_in_row=3) -class VerifyCog(Scale): +class VerifyCog(Cog): """JARVIS Verify Cog.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) @@ -108,6 +108,6 @@ class VerifyCog(Scale): self.logger.debug(f"User {ctx.author.id} failed to verify before timeout") -def setup(bot: Snake) -> None: +def setup(bot: Client) -> None: """Add VerifyCog to JARVIS""" VerifyCog(bot) diff --git a/jarvis/config.py b/jarvis/config.py index 50b9310..98399e5 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -16,7 +16,7 @@ class JarvisConfig(CConfig): OPTIONAL = { "sync": False, "log_level": "WARNING", - "scales": None, + "cogs": None, "events": True, "gitlab_token": None, "max_messages": 1000, diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index aec9c96..0c56a59 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -3,9 +3,9 @@ from datetime import datetime, timezone from pkgutil import iter_modules import git -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.guild import AuditLogEntry -from dis_snek.models.discord.user import Member +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.guild import AuditLogEntry +from naff.models.discord.user import Member from jarvis.config import get_config diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index ce44146..8fd65f0 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,8 +1,6 @@ """Cog wrapper for command caching.""" import logging -from dis_snek import InteractionContext, Scale, Snake -from dis_snek.models.discord.embed import EmbedField from jarvis_core.db import q from jarvis_core.db.models import ( Action, @@ -14,6 +12,8 @@ from jarvis_core.db.models import ( Setting, Warning, ) +from naff import Client, Cog, InteractionContext +from naff.models.discord.embed import EmbedField from jarvis.utils import build_embed @@ -21,13 +21,13 @@ MODLOG_LOOKUP = {"Ban": Ban, "Kick": Kick, "Mute": Mute, "Warning": Warning} IGNORE_COMMANDS = {"Ban": ["bans"], "Kick": [], "Mute": ["unmute"], "Warning": ["warnings"]} -class ModcaseCog(Scale): +class ModcaseCog(Cog): """Cog wrapper for moderation case logging.""" - def __init__(self, bot: Snake): + def __init__(self, bot: Client): self.bot = bot self.logger = logging.getLogger(__name__) - self.add_scale_postrun(self.log) + self.add_cog_postrun(self.log) async def log(self, ctx: InteractionContext, *_args: list, **kwargs: dict) -> None: """ diff --git a/jarvis/utils/embeds.py b/jarvis/utils/embeds.py index c0fcba1..3d9e929 100644 --- a/jarvis/utils/embeds.py +++ b/jarvis/utils/embeds.py @@ -1,6 +1,6 @@ """JARVIS bot-specific embeds.""" -from dis_snek.models.discord.embed import Embed, EmbedField -from dis_snek.models.discord.user import Member +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.user import Member from jarvis.utils import build_embed diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index 1eacbc7..3ad2900 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -1,5 +1,5 @@ """Permissions wrappers.""" -from dis_snek import InteractionContext, Permissions +from naff import InteractionContext, Permissions from jarvis.config import get_config diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 726c777..cf78346 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -9,15 +9,15 @@ from types import FunctionType, ModuleType from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional import git -from dis_snek.client.utils.misc_utils import find, find_all -from dis_snek.models.snek.application_commands import SlashCommand -from dis_snek.models.snek.scale import Scale +from naff.client.utils.misc_utils import find, find_all +from naff.models.naff.application_commands import SlashCommand +from naff.models.naff.cog import Cog from rich.table import Table import jarvis.cogs if TYPE_CHECKING: - from dis_snek.client.client import Snake + from naff.client.client import Client _logger = logging.getLogger(__name__) @@ -40,7 +40,7 @@ def get_all_commands(module: ModuleType = jarvis.cogs) -> Dict[str, Callable]: commands = {} def validate_ires(entry: Any) -> bool: - return isclass(entry) and issubclass(entry, Scale) and entry is not Scale + return isclass(entry) and issubclass(entry, Cog) and entry is not Cog def validate_cog(cog: FunctionType) -> bool: return isinstance(cog, SlashCommand) @@ -113,7 +113,7 @@ def get_git_changes(repo: git.Repo) -> dict: } -async def update(bot: "Snake") -> Optional[UpdateResult]: +async def update(bot: "Client") -> Optional[UpdateResult]: """ Update JARVIS and return an UpdateResult. @@ -150,17 +150,17 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: for module in current_commands.keys(): if module not in new_commands: logger.debug("Module %s removed after update", module) - bot.shed_scale(module) + bot.shed_Cog(module) unloaded.append(module) logger.debug("Checking for new/modified commands") for module, commands in new_commands.items(): logger.debug("Processing %s", module) if module not in current_commands: - bot.grow_scale(module) + bot.grow_Cog(module) loaded.append(module) elif len(current_commands[module]) != len(commands): - bot.regrow_scale(module) + bot.regrow_Cog(module) reloaded.append(module) else: for command in commands: @@ -182,15 +182,15 @@ async def update(bot: "Snake") -> Optional[UpdateResult]: # Check if number arguments have changed if len(old_args) != len(new_args): - bot.regrow_scale(module) + bot.regrow_Cog(module) reloaded.append(module) elif any(x not in old_arg_names for x in new_arg_names) or any( x not in new_arg_names for x in old_arg_names ): - bot.regrow_scale(module) + bot.regrow_Cog(module) reloaded.append(module) elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - bot.regrow_scale(module) + bot.regrow_Cog(module) reloaded.append(module) return UpdateResult( From e17d05873e06cc16591b9a55ee808949620c64b9 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:05:25 -0600 Subject: [PATCH 309/365] Add smarter moderation cases --- jarvis/client.py | 73 ++++++++++++++++++++++++++++++- jarvis/config.py | 87 +------------------------------------ jarvis/utils/__init__.py | 4 +- jarvis/utils/cogs.py | 58 +++++++++++++++++++------ jarvis/utils/permissions.py | 7 +-- poetry.lock | 27 ++++++++++-- pyproject.toml | 1 + 7 files changed, 147 insertions(+), 110 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index f5275b7..3c07e9c 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -6,7 +6,16 @@ from datetime import datetime, timedelta, timezone from aiohttp import ClientSession from jarvis_core.db import q -from jarvis_core.db.models import Autopurge, Autoreact, Roleping, Setting, Warning +from jarvis_core.db.models import ( + Action, + Autopurge, + Autoreact, + Modlog, + Note, + Roleping, + Setting, + Warning, +) from jarvis_core.filters import invites, url from jarvis_core.util.ansi import RESET, Fore, Format, fmt from naff import Client, listen @@ -17,6 +26,7 @@ from naff.api.events.discord import ( MessageDelete, MessageUpdate, ) +from naff.api.events.internal import Button from naff.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException from naff.client.utils.misc_utils import find_all from naff.models.discord.channel import DMChannel @@ -57,7 +67,9 @@ CMD_FMT = fmt(Fore.GREEN, Format.BOLD) class Jarvis(Client): def __init__(self, *args, **kwargs): # noqa: ANN002 ANN003 + redis = kwargs.pop("redis") super().__init__(*args, **kwargs) + self.redis = redis self.logger = logging.getLogger(__name__) self.phishing_domains = [] self.pre_run_callback = self._prerun @@ -634,3 +646,62 @@ class Jarvis(Client): self.logger.warning( f"Failed to process edit {message.guild.id}/{message.channel.id}/{message.id}: {e}" ) + + @listen() + async def on_button(self, event: Button) -> None: + """Process button events.""" + context = event.context + await context.defer(ephemeral=True) + if not context.custom_id.startswith("modcase|"): + return await super().on_button(event) + + if not context.author.has_permissions(Permissions.MANAGE_USERS): + return + + user_key = f"msg|{context.message.id}" + + if context.custom_id == "modcase|yes": + if user_id := await self.redis.get(user_key): + action_key = f"{user_id}|{context.guild.id}" + if (user := await context.guild.fetch_member(user_id)) and ( + action_data := await self.redis.get(action_key) + ): + name, parent = action_data.split("|")[:2] + action = Action(action_type=name, parent=parent) + note = Note( + admin=context.author.id, content="Moderation case opened via message" + ) + modlog = Modlog( + user=user.id, admin=context.author.id, actions=[action], notes=[note] + ) + await modlog.commit() + + fields = ( + EmbedField(name="Admin", value=context.author.mention), + EmbedField(name="Opening Action", value=f"{name} {parent}"), + ) + embed = build_embed( + title="Moderation Case Opened", + description="Moderation case opened against {user.mention}", + fields=fields, + ) + embed.set_author( + name=user.username + "#" + user.discriminator, + icon_url=user.display_avatar.url, + ) + + await context.message.edit(embed=embed) + elif not user: + self.logger.debug("User no longer in guild") + await context.send("User no longer in guild", ephemeral=True) + else: + self.logger.warn("Unable to get action data ( %s )", action_key) + await context.send("Unable to get action data", ephemeral=True) + + await self.bot.redis.delete(user_key) + await self.bot.redis.delete(action_key) + + for row in context.message.components: + for component in row.components: + component.disabled = True + await context.message.edit(components=context.message.components) diff --git a/jarvis/config.py b/jarvis/config.py index 98399e5..5159b7c 100644 --- a/jarvis/config.py +++ b/jarvis/config.py @@ -1,18 +1,9 @@ """Load the config for JARVIS""" -import os - from jarvis_core.config import Config as CConfig -from pymongo import MongoClient -from yaml import load - -try: - from yaml import CLoader as Loader -except ImportError: - from yaml import Loader class JarvisConfig(CConfig): - REQUIRED = ("token", "mongo", "urls") + REQUIRED = ("token", "mongo", "urls", "redis") OPTIONAL = { "sync": False, "log_level": "WARNING", @@ -24,79 +15,3 @@ class JarvisConfig(CConfig): "reddit": None, "rook_token": None, } - - -class Config(object): - """Config singleton object for JARVIS""" - - def __new__(cls, *args: list, **kwargs: dict): - """Get the singleton config, or creates a new one.""" - it = cls.__dict__.get("it") - if it is not None: - return it - cls.__it__ = it = object.__new__(cls) - it.init(*args, **kwargs) - return it - - def init( - self, - token: str, - mongo: dict, - urls: dict, - sync: bool = False, - log_level: str = "WARNING", - cogs: list = None, - events: bool = True, - gitlab_token: str = None, - max_messages: int = 1000, - twitter: dict = None, - reddit: dict = None, - rook_token: str = None, - ) -> None: - """Initialize the config object.""" - self.token = token - self.mongo = mongo - self.urls = urls - self.log_level = log_level - self.cogs = cogs - self.events = events - self.max_messages = max_messages - self.gitlab_token = gitlab_token - self.twitter = twitter - self.reddit = reddit - self.sync = sync or os.environ.get("SYNC_COMMANDS", False) - self.rook_token = rook_token - self.__db_loaded = False - self.__mongo = MongoClient(**self.mongo["connect"]) - - def get_db_config(self) -> None: - """Load the database config objects.""" - if not self.__db_loaded: - db = self.__mongo[self.mongo["database"]] - items = db.config.find() - for item in items: - setattr(self, item["key"], item["value"]) - self.__db_loaded = True - - @classmethod - def from_yaml(cls, y: dict) -> "Config": - """Load the yaml config file.""" - return cls(**y) - - -def get_config(path: str = "config.yaml") -> Config: - """Get the config from the specified yaml file.""" - if Config.__dict__.get("it"): - return Config() - with open(path) as f: - raw = f.read() - y = load(raw, Loader=Loader) - config = Config.from_yaml(y) - config.get_db_config() - return config - - -def reload_config() -> None: - """Force reload of the config singleton on next call.""" - if "it" in Config.__dict__: - Config.__dict__.pop("it") diff --git a/jarvis/utils/__init__.py b/jarvis/utils/__init__.py index 0c56a59..2bece3a 100644 --- a/jarvis/utils/__init__.py +++ b/jarvis/utils/__init__.py @@ -7,7 +7,7 @@ from naff.models.discord.embed import Embed, EmbedField from naff.models.discord.guild import AuditLogEntry from naff.models.discord.user import Member -from jarvis.config import get_config +from jarvis.config import JarvisConfig def build_embed( @@ -63,7 +63,7 @@ def modlog_embed( def get_extensions(path: str) -> list: """Get JARVIS cogs.""" - config = get_config() + config = JarvisConfig.from_yaml() vals = config.cogs or [x.name for x in iter_modules(path)] return [f"jarvis.cogs.{x}" for x in vals] diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 8fd65f0..1675a58 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -2,17 +2,9 @@ import logging from jarvis_core.db import q -from jarvis_core.db.models import ( - Action, - Ban, - Kick, - Modlog, - Mute, - Note, - Setting, - Warning, -) +from jarvis_core.db.models import Ban, Kick, Mute, Setting, Warning from naff import Client, Cog, InteractionContext +from naff.models.discord.components import ActionRow, Button, ButtonStyles from naff.models.discord.embed import EmbedField from jarvis.utils import build_embed @@ -55,9 +47,6 @@ class ModcaseCog(Cog): self.logger.warning("Missing action %s, exiting", name) return - action = Action(action_type=name.lower(), parent=action.id) - note = Note(admin=self.bot.user.id, content="Moderation case opened automatically") - await Modlog(user=user.id, admin=ctx.author.id, actions=[action], notes=[note]).commit() notify = await Setting.find_one(q(guild=ctx.guild.id, setting="notify", value=True)) if notify and name not in ("Kick", "Ban"): # Ignore Kick and Ban, as these are unique fields = ( @@ -74,4 +63,45 @@ class ModcaseCog(Cog): guild_url = f"https://discord.com/channels/{ctx.guild.id}" embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon.url, url=guild_url) embed.set_thumbnail(url=ctx.guild.icon.url) - await user.send(embed=embed) + try: + await user.send(embed=embed) + except Exception: + self.logger.debug("User not warned of action due to closed DMs") + + lookup_key = f"{user.id}|{ctx.guild.id}" + + async with self.bot.redis.lock(lookup_key): + if await self.bot.redis.get(lookup_key): + self.logger.debug(f"User {user.id} in {ctx.guild.id} already has pending case") + return + + modlog = await Setting.find_one(q(guild=ctx.guild.id, setting="modlog")) + if not modlog: + return + + channel = await ctx.guild.fetch_channel(modlog.value) + if not channel: + self.logger.warn( + f"Guild {ctx.guild.id} modlog channel no longer exists, deleting" + ) + await modlog.delete() + return + + embed = build_embed( + title="Recent Action Taken", + description="Would you like to open a moderation case for {user.mention}?", + fields=[], + ) + embed.set_author( + name=user.username + "#" + user.discriminator, icon_url=user.display_avatar.url + ) + components = [ + ActionRow( + Button(style=ButtonStyles.RED, emoji="✖️", custom_id="modcase|no"), + Button(style=ButtonStyles.GREEN, emoji="✔️", custom_id="modcase|yes"), + ) + ] + message = await ctx.send(embed=embed, components=components) + await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}") + + await self.bot.redis.set(f"msg|{message.id}", user.id) diff --git a/jarvis/utils/permissions.py b/jarvis/utils/permissions.py index 3ad2900..cac2ff2 100644 --- a/jarvis/utils/permissions.py +++ b/jarvis/utils/permissions.py @@ -1,7 +1,7 @@ """Permissions wrappers.""" from naff import InteractionContext, Permissions -from jarvis.config import get_config +from jarvis.config import JarvisConfig def user_is_bot_admin() -> bool: @@ -9,8 +9,9 @@ def user_is_bot_admin() -> bool: async def predicate(ctx: InteractionContext) -> bool: """Command check predicate.""" - if getattr(get_config(), "admins", None): - return ctx.author.id in get_config().admins + cfg = JarvisConfig.from_yaml() + if getattr(cfg, "admins", None): + return ctx.author.id in cfg.admins else: return False diff --git a/poetry.lock b/poetry.lock index 9100b39..f14ec08 100644 --- a/poetry.lock +++ b/poetry.lock @@ -40,6 +40,21 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotli", "cchardet"] +[[package]] +name = "aioredis" +version = "2.0.1" +description = "asyncio (PEP 3156) Redis support" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = "*" +typing-extensions = "*" + +[package.extras] +hiredis = ["hiredis (>=1.0)"] + [[package]] name = "aiosignal" version = "1.2.0" @@ -497,11 +512,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -901,7 +916,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "3bfe48a36c3bc4bef6e6840eaeb51b01ffd6d038135451e45307eb843df981e0" +content-hash = "3fe4606bc1a4c1e58ee535a4b4126f676c0780c2fd02d15e3df9657586967b1e" [metadata.files] aiofile = [ @@ -986,6 +1001,10 @@ aiohttp = [ {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] +aioredis = [ + {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, + {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, +] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, diff --git a/pyproject.toml b/pyproject.toml index 7e0bdaf..90e6f18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ asyncpraw = "^7.5.0" rook = "^0.1.170" rich = "^12.3.0" jurigged = "^0.5.0" +aioredis = "^2.0.1" [build-system] requires = ["poetry-core>=1.0.0"] From 6bd0b7f1a87e45866665a014f99b372659bbbf00 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:07:27 -0600 Subject: [PATCH 310/365] Update poetry.lock --- poetry.lock | 57 ++++---------------------------------------------- pyproject.toml | 1 - 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/poetry.lock b/poetry.lock index f14ec08..6cda68b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -251,35 +251,6 @@ calendars = ["convertdate", "hijri-converter", "convertdate"] fasttext = ["fasttext"] langdetect = ["langdetect"] -[[package]] -name = "dis-snek" -version = "8.0.0" -description = "An API wrapper for Discord filled with snakes" -category = "main" -optional = false -python-versions = ">=3.10" - -[package.dependencies] -aiohttp = "*" -attrs = "*" -discord-typings = "*" -tomli = "*" - -[package.extras] -all = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] -voice = ["PyNaCl (>=1.5.0,<1.6)", "yt-dlp"] - -[[package]] -name = "discord-typings" -version = "0.4.0" -description = "Maintained typings of payloads that Discord sends" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing_extensions = ">=4,<5" - [[package]] name = "distro" version = "1.7.0" @@ -512,11 +483,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -756,14 +727,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - [[package]] name = "tweepy" version = "4.8.0" @@ -916,7 +879,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "3fe4606bc1a4c1e58ee535a4b4126f676c0780c2fd02d15e3df9657586967b1e" +content-hash = "8bb2b59de1ccb8f5e5588ae3ac600e7fb6d7f638224c9cc24228f79e666aec63" [metadata.files] aiofile = [ @@ -1082,14 +1045,6 @@ dateparser = [ {file = "dateparser-1.1.1-py2.py3-none-any.whl", hash = "sha256:9600874312ff28a41f96ec7ccdc73be1d1c44435719da47fea3339d55ff5a628"}, {file = "dateparser-1.1.1.tar.gz", hash = "sha256:038196b1f12c7397e38aad3d61588833257f6f552baa63a1499e6987fa8d42d9"}, ] -dis-snek = [ - {file = "dis-snek-8.0.0.tar.gz", hash = "sha256:c035a4f664f9a638b80089f2a9a3330a4254fc227ef2c83c96582df06f392281"}, - {file = "dis_snek-8.0.0-py3-none-any.whl", hash = "sha256:3a89c8f78c27407fb67d42dfaa51be6a507306306779e45cd47687bd846b3b23"}, -] -discord-typings = [ - {file = "discord-typings-0.4.0.tar.gz", hash = "sha256:66bce666194e8f006914f788f940265c009cce9b63f9a8ce2bc7931d3d3ef11c"}, - {file = "discord_typings-0.4.0-py3-none-any.whl", hash = "sha256:a390b614cbcbd82af083660c12e46536b4b790ac026f8d43bdd11c8953837ca0"}, -] distro = [ {file = "distro-1.7.0-py3-none-any.whl", hash = "sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b"}, {file = "distro-1.7.0.tar.gz", hash = "sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39"}, @@ -1756,10 +1711,6 @@ smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] tweepy = [ {file = "tweepy-4.8.0-py2.py3-none-any.whl", hash = "sha256:f281bb53ab3ba999ff5e3d743d92d3ed543ee5551c7250948f9e56190ec7a43e"}, {file = "tweepy-4.8.0.tar.gz", hash = "sha256:8ba5774ac1663b09e5fce1b030daf076f2c9b3ddbf2e7e7ea0bae762e3b1fe3e"}, diff --git a/pyproject.toml b/pyproject.toml index 90e6f18..a379526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ authors = ["Zevaryx "] [tool.poetry.dependencies] python = "^3.10" PyYAML = "^6.0" -dis-snek = "*" GitPython = "^3.1.26" mongoengine = "^0.23.1" opencv-python = "^4.5.5" From 8226320640b6c6708f6cba07a381792345fcfb73 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:08:13 -0600 Subject: [PATCH 311/365] Add redis to client init --- jarvis/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index 8a17dd0..ba9c9ed 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -36,6 +36,7 @@ async def run() -> None: sync_interactions=jconfig.sync, delete_unused_application_cmds=True, send_command_tracebacks=False, + redis=jconfig.redis, ) if jconfig.log_level == "DEBUG": From cd78b0e002891a5d7bde23b6cd600a2bb19df133 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:09:10 -0600 Subject: [PATCH 312/365] Fix dbrand config error --- jarvis/cogs/dbrand.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/dbrand.py b/jarvis/cogs/dbrand.py index 9000e80..f42bfae 100644 --- a/jarvis/cogs/dbrand.py +++ b/jarvis/cogs/dbrand.py @@ -13,7 +13,7 @@ from naff.models.naff.application_commands import ( from naff.models.naff.command import cooldown from naff.models.naff.cooldowns import Buckets -from jarvis.config import get_config +from jarvis.config import JarvisConfig from jarvis.data.dbrand import shipping_lookup from jarvis.utils import build_embed @@ -33,7 +33,7 @@ class DbrandCog(Cog): self.base_url = "https://dbrand.com/" self._session = aiohttp.ClientSession() self._session.headers.update({"Content-Type": "application/json"}) - self.api_url = get_config().urls["dbrand_shipping"] + self.api_url = JarvisConfig.from_yaml().urls["dbrand_shipping"] self.cache = {} def __del__(self): From 7b3cf0dc950feedfb88e920f2f3fc8cd5d813eab Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:11:49 -0600 Subject: [PATCH 313/365] Construct redis instance to pass into client --- jarvis/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index ba9c9ed..e30ff64 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -1,6 +1,7 @@ """Main JARVIS package.""" import logging +import aioredis import jurigged import rook from jarvis_core.db import connect @@ -30,13 +31,17 @@ async def run() -> None: intents = ( Intents.DEFAULT | Intents.MESSAGES | Intents.GUILD_MEMBERS | Intents.GUILD_MESSAGE_CONTENT ) + redis_config = jconfig.redis.copy() + redis_host = redis_config.pop("host") + + redis = await aioredis.from_url(redis_host, **redis_config) jarvis = Jarvis( intents=intents, sync_interactions=jconfig.sync, delete_unused_application_cmds=True, send_command_tracebacks=False, - redis=jconfig.redis, + redis=redis, ) if jconfig.log_level == "DEBUG": From 964e646eeb3224a3778bb0a3b24f503f81e9f501 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:16:21 -0600 Subject: [PATCH 314/365] Differentiate lock from lookup key --- jarvis/utils/cogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 1675a58..49f4d7a 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -70,7 +70,7 @@ class ModcaseCog(Cog): lookup_key = f"{user.id}|{ctx.guild.id}" - async with self.bot.redis.lock(lookup_key): + async with self.bot.redis.lock("lock|" + lookup_key): if await self.bot.redis.get(lookup_key): self.logger.debug(f"User {user.id} in {ctx.guild.id} already has pending case") return From c0cc0f8b9e3becb92db1f716b1dbf1481cdda945 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:20:50 -0600 Subject: [PATCH 315/365] Fix permission lookup, send channel --- jarvis/client.py | 2 +- jarvis/utils/cogs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 3c07e9c..517b228 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -655,7 +655,7 @@ class Jarvis(Client): if not context.custom_id.startswith("modcase|"): return await super().on_button(event) - if not context.author.has_permissions(Permissions.MANAGE_USERS): + if not context.author.has_permission(Permissions.MANAGE_USERS): return user_key = f"msg|{context.message.id}" diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 49f4d7a..d2884b9 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -101,7 +101,7 @@ class ModcaseCog(Cog): Button(style=ButtonStyles.GREEN, emoji="✔️", custom_id="modcase|yes"), ) ] - message = await ctx.send(embed=embed, components=components) + message = await channel.send(embed=embed, components=components) await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}") await self.bot.redis.set(f"msg|{message.id}", user.id) From 0ca77dd0234bd81164461b10c5fca651619cadd4 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:22:31 -0600 Subject: [PATCH 316/365] Fix permission type --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index 517b228..a817728 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -655,7 +655,7 @@ class Jarvis(Client): if not context.custom_id.startswith("modcase|"): return await super().on_button(event) - if not context.author.has_permission(Permissions.MANAGE_USERS): + if not context.author.has_permission(Permissions.MODERATE_MEMBERS): return user_key = f"msg|{context.message.id}" From 1c30e27ec9de68ad30dbf19abeca3d2bc00db52c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:24:55 -0600 Subject: [PATCH 317/365] Better feedback --- jarvis/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index a817728..c5c5532 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -682,7 +682,7 @@ class Jarvis(Client): ) embed = build_embed( title="Moderation Case Opened", - description="Moderation case opened against {user.mention}", + description=f"Moderation case opened against {user.mention}", fields=fields, ) embed.set_author( @@ -705,3 +705,5 @@ class Jarvis(Client): for component in row.components: component.disabled = True await context.message.edit(components=context.message.components) + msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened" + await context.send(msg) From e1efb7be551f0efc3278fc38d403fe91f7ec167b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:27:42 -0600 Subject: [PATCH 318/365] Fix deletion area of keys to prevent false positives --- jarvis/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index c5c5532..e582190 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -659,6 +659,7 @@ class Jarvis(Client): return user_key = f"msg|{context.message.id}" + action_key = "" if context.custom_id == "modcase|yes": if user_id := await self.redis.get(user_key): @@ -698,12 +699,11 @@ class Jarvis(Client): self.logger.warn("Unable to get action data ( %s )", action_key) await context.send("Unable to get action data", ephemeral=True) - await self.bot.redis.delete(user_key) - await self.bot.redis.delete(action_key) - for row in context.message.components: for component in row.components: component.disabled = True await context.message.edit(components=context.message.components) msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened" await context.send(msg) + await self.bot.redis.delete(user_key) + await self.bot.redis.delete(action_key) From cbc07dfa8405e8dd1d29dbb69b47a3ff71e161fd Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:28:33 -0600 Subject: [PATCH 319/365] Fix text formatting --- jarvis/utils/cogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index d2884b9..d7c7a14 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -89,7 +89,7 @@ class ModcaseCog(Cog): embed = build_embed( title="Recent Action Taken", - description="Would you like to open a moderation case for {user.mention}?", + description=f"Would you like to open a moderation case for {user.mention}?", fields=[], ) embed.set_author( From 2f2aa20ed622dd793188d991c622f886088fa0f3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:29:14 -0600 Subject: [PATCH 320/365] Fix key deletion --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index e582190..272a0cd 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -705,5 +705,5 @@ class Jarvis(Client): await context.message.edit(components=context.message.components) msg = "Cancelled" if context.custom_id == "modcase|no" else "Moderation case opened" await context.send(msg) - await self.bot.redis.delete(user_key) - await self.bot.redis.delete(action_key) + await self.redis.delete(user_key) + await self.redis.delete(action_key) From e7c3b664dbe142aa7a743b4e9fbb3aeb3e54b6f8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:31:59 -0600 Subject: [PATCH 321/365] Add redis code_responses flag --- jarvis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/__init__.py b/jarvis/__init__.py index e30ff64..703bc1a 100644 --- a/jarvis/__init__.py +++ b/jarvis/__init__.py @@ -34,7 +34,7 @@ async def run() -> None: redis_config = jconfig.redis.copy() redis_host = redis_config.pop("host") - redis = await aioredis.from_url(redis_host, **redis_config) + redis = await aioredis.from_url(redis_host, decode_responses=True, **redis_config) jarvis = Jarvis( intents=intents, From 692200ea475c680f53c0255339146fccc9829592 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 01:36:32 -0600 Subject: [PATCH 322/365] Set expirations on interactive modlog keys --- jarvis/utils/cogs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index d7c7a14..fa5236a 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -1,5 +1,6 @@ """Cog wrapper for command caching.""" import logging +from datetime import timedelta from jarvis_core.db import q from jarvis_core.db.models import Ban, Kick, Mute, Setting, Warning @@ -102,6 +103,8 @@ class ModcaseCog(Cog): ) ] message = await channel.send(embed=embed, components=components) - await self.bot.redis.set(lookup_key, f"{name.lower()}|{action.id}") - await self.bot.redis.set(f"msg|{message.id}", user.id) + await self.bot.redis.set( + lookup_key, f"{name.lower()}|{action.id}", ex=timedelta(days=7) + ) + await self.bot.redis.set(f"msg|{message.id}", user.id, ex=timedelta(days=7)) From 3eee7aa2a362b04c3b3febc6cb1acc819de64601 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 08:45:13 -0600 Subject: [PATCH 323/365] Add action to existing open case if one exists --- jarvis/client.py | 6 +++++- jarvis/utils/cogs.py | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 272a0cd..297314a 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -673,7 +673,11 @@ class Jarvis(Client): admin=context.author.id, content="Moderation case opened via message" ) modlog = Modlog( - user=user.id, admin=context.author.id, actions=[action], notes=[note] + user=user.id, + admin=context.author.id, + guild=context.guild.id, + actions=[action], + notes=[note], ) await modlog.commit() diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index fa5236a..91fe055 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -3,7 +3,7 @@ import logging from datetime import timedelta from jarvis_core.db import q -from jarvis_core.db.models import Ban, Kick, Mute, Setting, Warning +from jarvis_core.db.models import Action, Ban, Kick, Modlog, Mute, Setting, Warning from naff import Client, Cog, InteractionContext from naff.models.discord.components import ActionRow, Button, ButtonStyles from naff.models.discord.embed import EmbedField @@ -69,6 +69,13 @@ class ModcaseCog(Cog): except Exception: self.logger.debug("User not warned of action due to closed DMs") + modlog = await Modlog.find_onw(q(user=user.id, guild=ctx.guild.id, open=True)) + if modlog: + m_action = Action(action_type=name.lower(), parent=action.id) + modlog.actions.append(m_action) + await modlog.commit() + return + lookup_key = f"{user.id}|{ctx.guild.id}" async with self.bot.redis.lock("lock|" + lookup_key): From 085b2325a73f6660c9b027edbfbcb708d18170a3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 09:48:53 -0600 Subject: [PATCH 324/365] Add modcase list command --- jarvis/cogs/admin/modcase.py | 172 +++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 jarvis/cogs/admin/modcase.py diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py new file mode 100644 index 0000000..cc1bb05 --- /dev/null +++ b/jarvis/cogs/admin/modcase.py @@ -0,0 +1,172 @@ +"""JARVIS Moderation Case management.""" +from typing import TYPE_CHECKING, Optional + +from jarvis_core.db import q +from jarvis_core.db.models import Modlog, actions +from naff import Client, Cog, InteractionContext, Permissions +from naff.ext.paginators import Paginator +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) +from naff.models.naff.command import check +from rich.console import Console +from rich.table import Table + +from jarvis.utils import build_embed +from jarvis.utils.permissions import admin_or_permissions + +if TYPE_CHECKING: + from naff.models.discord.guild import Guild + +ACTIONS_LOOKUP = { + "ban": actions.Ban, + "kick": actions.Kick, + "mute": actions.Mute, + "unban": actions.Unban, + "warning": actions.Warning, +} + + +class CaseCog(Cog): + """JARVIS CaseCog.""" + + async def get_embed(self, mod_case: Modlog, guild: "Guild") -> Embed: + """ + Get Moderation case embed. + + Args: + mod_case: Moderation case + guild: Originating guild + """ + action_table = Table(title="Actions", show_header=False) + action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True) + action_table.add_column( + header="Admin", justify="left", style="light_sky_blue1", no_wrap=True + ) + action_table.add_column(header="Reason", justify="left", style="white") + + note_table = Table(title="Notes") + note_table.add_column(header="Admin", justify="left", style="light_sky_blue1", no_wrap=True) + note_table.add_column(header="Content", justify="left", style="white") + + console = Console() + + action_output = "" + for action in mod_case.actions: + parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) + if not parent_action: + action.orphaned = True + action_table.add_row(action.action_type.title(), "N/A", "N/A") + else: + admin = await self.bot.fetch_user(parent_action.admin) + admin_text = "Deleted User" + if admin: + admin_text = f"{admin.username}#{admin.discriminator}" + action_table.add_row(action.action_type.title(), admin_text, parent_action.reason) + with console.capture() as cap: + console.print(action_table) + + tmp_output = cap.get() + if len(tmp_output) >= 1000: + break + + action_output = tmp_output + + note_output = "" + for note in sorted(mod_case.notes, lambda x: x.created_at): + admin = await self.bot.fetch_user(note.admin) + admin_text = "N/A" + if not admin: + admin_text = f"{admin.username}#{admin.discriminator}" + note_table.add_row(admin_text, note.content) + + with console.capture() as cap: + console.print(note_table) + + tmp_output = cap.get() + if len(tmp_output) >= 1000: + break + + note_output = tmp_output + + status = "Open" if mod_case.open else "Closed" + + user = await self.bot.fetch_user(mod_case.user) + username = "Deleted User" + user_text = "Deleted User" + if user: + username = f"{user.username}#{user.discriminator}" + user_text = user.mention + + admin = await self.bot.fetch_user(mod_case.admin) + admin_text = "[N/A]" + if admin: + admin_text = admin.mention + + ts = int(mod_case.created_at.timestamp()) + + action_output = f"```ansi\n{action_output}\n```" + note_output = f"```ansi\n{note_output}\n```" + + fields = ( + EmbedField(name="Opened By", value=admin_text), + EmbedField(name="Opened At", value=f""), + EmbedField(name="Actions", value=action_output), + EmbedField(name="Notes", value=note_output), + ) + + embed = build_embed( + title="Moderation Case", description=f"{status} case against {user_text}", fields=fields + ) + icon_url = None + if user: + icon_url = user.avatar.url + + embed.set_author(name=username, icon_url=icon_url) + embed.set_footer(text=str(mod_case.user)) + + await mod_case.commit() + return embed + + cases = SlashCommand(name="cases", description="Manage moderation cases") + + @cases.subcommand(sub_cmd_name="list", sub_cmd_description="List moderation cases") + @slash_option( + name="user", + description="User to filter cases to", + opt_type=OptionTypes.USER, + required=False, + ) + @slash_option( + name="closed", + description="Include closed cases", + opt_type=OptionTypes.BOOLEAN, + required=False, + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _cases_list( + self, ctx: InteractionContext, user: Optional[Member] = None, closed: bool = False + ) -> None: + query = q(guild=ctx.guild.id) + if not closed: + query.update(q(open=True)) + if user: + query.update(q(user=user.id)) + cases = await Modlog.find(query).sort("created_at", -1).to_list(None) + + if len(cases) == 0: + await ctx.send("No cases to view", ephemeral=True) + return + + pages = [await self.get_embed(c, ctx.guild) for c in cases] + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) + await paginator.send(ctx) + + +def setup(bot: Client) -> None: + """Add CaseCog to JARVIS""" + CaseCog(bot) From 25a436c42beb065caa7d5de9fd55d648ed0bef39 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 09:50:23 -0600 Subject: [PATCH 325/365] Fix typos from naff migration --- jarvis/utils/updates.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index cf78346..3f556f7 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -150,17 +150,17 @@ async def update(bot: "Client") -> Optional[UpdateResult]: for module in current_commands.keys(): if module not in new_commands: logger.debug("Module %s removed after update", module) - bot.shed_Cog(module) + bot.shed_cog(module) unloaded.append(module) logger.debug("Checking for new/modified commands") for module, commands in new_commands.items(): logger.debug("Processing %s", module) if module not in current_commands: - bot.grow_Cog(module) + bot.grow_cog(module) loaded.append(module) elif len(current_commands[module]) != len(commands): - bot.regrow_Cog(module) + bot.regrow_cog(module) reloaded.append(module) else: for command in commands: @@ -182,15 +182,15 @@ async def update(bot: "Client") -> Optional[UpdateResult]: # Check if number arguments have changed if len(old_args) != len(new_args): - bot.regrow_Cog(module) + bot.regrow_cog(module) reloaded.append(module) elif any(x not in old_arg_names for x in new_arg_names) or any( x not in new_arg_names for x in old_arg_names ): - bot.regrow_Cog(module) + bot.regrow_cog(module) reloaded.append(module) elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - bot.regrow_Cog(module) + bot.regrow_cog(module) reloaded.append(module) return UpdateResult( From 1ddef9fd0a8d315d1458aedc0e0161018fe714d7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 09:52:13 -0600 Subject: [PATCH 326/365] Add loader for modcase --- jarvis/cogs/admin/__init__.py | 16 ++++++++++++++-- jarvis/cogs/admin/modcase.py | 7 +------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/jarvis/cogs/admin/__init__.py b/jarvis/cogs/admin/__init__.py index 6e2a8f8..e524d89 100644 --- a/jarvis/cogs/admin/__init__.py +++ b/jarvis/cogs/admin/__init__.py @@ -3,7 +3,17 @@ import logging from naff import Client -from jarvis.cogs.admin import ban, kick, lock, lockdown, mute, purge, roleping, warning +from jarvis.cogs.admin import ( + ban, + kick, + lock, + lockdown, + modcase, + mute, + purge, + roleping, + warning, +) def setup(bot: Client) -> None: @@ -17,7 +27,9 @@ def setup(bot: Client) -> None: lock.LockCog(bot) logger.debug(msg.format("lock")) lockdown.LockdownCog(bot) - logger.debug(msg.format("ban")) + logger.debug(msg.format("lockdown")) + modcase.CaseCog(bot) + logger.debug(msg.format("modcase")) mute.MuteCog(bot) logger.debug(msg.format("mute")) purge.PurgeCog(bot) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index cc1bb05..295aa34 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Optional from jarvis_core.db import q from jarvis_core.db.models import Modlog, actions -from naff import Client, Cog, InteractionContext, Permissions +from naff import Cog, InteractionContext, Permissions from naff.ext.paginators import Paginator from naff.models.discord.embed import Embed, EmbedField from naff.models.discord.user import Member @@ -165,8 +165,3 @@ class CaseCog(Cog): pages = [await self.get_embed(c, ctx.guild) for c in cases] paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) await paginator.send(ctx) - - -def setup(bot: Client) -> None: - """Add CaseCog to JARVIS""" - CaseCog(bot) From 17b78e5f4eea7d81d10e64e03ed061349e7fba71 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 10:07:15 -0600 Subject: [PATCH 327/365] Fix sorted call, typo in ModcaseCog --- jarvis/cogs/admin/modcase.py | 2 +- jarvis/utils/cogs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 295aa34..b035c8e 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -77,7 +77,7 @@ class CaseCog(Cog): action_output = tmp_output note_output = "" - for note in sorted(mod_case.notes, lambda x: x.created_at): + for note in sorted(mod_case.notes, key=lambda x: x.created_at): admin = await self.bot.fetch_user(note.admin) admin_text = "N/A" if not admin: diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 91fe055..246cf51 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -69,7 +69,7 @@ class ModcaseCog(Cog): except Exception: self.logger.debug("User not warned of action due to closed DMs") - modlog = await Modlog.find_onw(q(user=user.id, guild=ctx.guild.id, open=True)) + modlog = await Modlog.find_one(q(user=user.id, guild=ctx.guild.id, open=True)) if modlog: m_action = Action(action_type=name.lower(), parent=action.id) modlog.actions.append(m_action) From 531239db12c28ea83b930b3cf7939dc3e3907f1c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 10:16:47 -0600 Subject: [PATCH 328/365] Fix formatting on modcase tables --- jarvis/cogs/admin/modcase.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index b035c8e..7287606 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -42,15 +42,13 @@ class CaseCog(Cog): mod_case: Moderation case guild: Originating guild """ - action_table = Table(title="Actions", show_header=False) + action_table = Table() action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True) - action_table.add_column( - header="Admin", justify="left", style="light_sky_blue1", no_wrap=True - ) + action_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True) action_table.add_column(header="Reason", justify="left", style="white") - note_table = Table(title="Notes") - note_table.add_column(header="Admin", justify="left", style="light_sky_blue1", no_wrap=True) + note_table = Table() + note_table.add_column(header="Admin", justify="left", style="cyan", no_wrap=True) note_table.add_column(header="Content", justify="left", style="white") console = Console() @@ -80,7 +78,7 @@ class CaseCog(Cog): for note in sorted(mod_case.notes, key=lambda x: x.created_at): admin = await self.bot.fetch_user(note.admin) admin_text = "N/A" - if not admin: + if admin: admin_text = f"{admin.username}#{admin.discriminator}" note_table.add_row(admin_text, note.content) From 4965579897dff8f45bdb379937b0c9b67446edf7 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 10:25:33 -0600 Subject: [PATCH 329/365] Sort action items when logging in ModcaseCog --- jarvis/utils/cogs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 246cf51..25a07b6 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -43,7 +43,9 @@ class ModcaseCog(Cog): self.logger.warning("Unsupported action %s, exiting", name) return - action = await coll.find_one(q(user=user.id, guild=ctx.guild_id, active=True)) + action = await coll.find_one( + q(user=user.id, guild=ctx.guild_id, active=True), sort=[("_id", -1)] + ) if not action: self.logger.warning("Missing action %s, exiting", name) return From 2630cf4c92cb7f293a4aa75b196473dd021e9815 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 10:40:33 -0600 Subject: [PATCH 330/365] Update modcase styling to include extra info --- jarvis/cogs/admin/modcase.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 7287606..6ff5769 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -54,7 +54,8 @@ class CaseCog(Cog): console = Console() action_output = "" - for action in mod_case.actions: + action_output_extra = "" + for idx, action in enumerate(mod_case.actions): parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) if not parent_action: action.orphaned = True @@ -69,13 +70,16 @@ class CaseCog(Cog): console.print(action_table) tmp_output = cap.get() - if len(tmp_output) >= 1000: + if len(tmp_output) >= 800: + action_output_extra = f"... and {len(mod_case.actions[idx:])} more actions" break action_output = tmp_output note_output = "" - for note in sorted(mod_case.notes, key=lambda x: x.created_at): + note_output_extra = "" + notes = sorted(mod_case.notes, key=lambda x: x.created_at) + for idx, note in enumerate(notes): admin = await self.bot.fetch_user(note.admin) admin_text = "N/A" if admin: @@ -87,6 +91,7 @@ class CaseCog(Cog): tmp_output = cap.get() if len(tmp_output) >= 1000: + note_output_extra = f"... and {len(notes[idx:])} more notes" break note_output = tmp_output @@ -105,20 +110,19 @@ class CaseCog(Cog): if admin: admin_text = admin.mention - ts = int(mod_case.created_at.timestamp()) - - action_output = f"```ansi\n{action_output}\n```" - note_output = f"```ansi\n{note_output}\n```" + action_output = f"```ansi\n{action_output}\n{action_output_extra}\n```" + note_output = f"```ansi\n{note_output}\n{note_output_extra}\n```" fields = ( - EmbedField(name="Opened By", value=admin_text), - EmbedField(name="Opened At", value=f""), EmbedField(name="Actions", value=action_output), EmbedField(name="Notes", value=note_output), ) embed = build_embed( - title="Moderation Case", description=f"{status} case against {user_text}", fields=fields + title=f"Moderation Case [{mod_case.id}]", + description=f"{status} case against {user_text} opened by {admin_text}", + fields=fields, + timestamp=mod_case.created_at, ) icon_url = None if user: From 144aff69898726727fbe3c2f8958f485e81095ab Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 10:44:21 -0600 Subject: [PATCH 331/365] Change embed description --- jarvis/cogs/admin/modcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 6ff5769..52faccf 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -120,7 +120,7 @@ class CaseCog(Cog): embed = build_embed( title=f"Moderation Case [{mod_case.id}]", - description=f"{status} case against {user_text} opened by {admin_text}", + description=f"{status} case against {user_text} [**opened by {admin_text}**]", fields=fields, timestamp=mod_case.created_at, ) From 20dad49173782955249205c5e90c1a1329e67eba Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 14:27:58 -0600 Subject: [PATCH 332/365] Add nanoid --- poetry.lock | 18 +++++++++++++++--- pyproject.toml | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6cda68b..172c2a4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -307,7 +307,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.9.0" +version = "0.9.1" description = "JARVIS core" category = "main" optional = false @@ -327,7 +327,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "29f90a8dc4e062d2d59f671b4cef2a61009dfde0" +resolved_reference = "aa13a556d67e926aca6e3053aa383d9cdf5a88b1" [[package]] name = "jinxed" @@ -407,6 +407,14 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "nanoid" +version = "2.0.0" +description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "numpy" version = "1.22.3" @@ -879,7 +887,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "8bb2b59de1ccb8f5e5588ae3ac600e7fb6d7f638224c9cc24228f79e666aec63" +content-hash = "a0e99de0ed0905f03abc1d97775dfc7dfb93fe5956a687f7d2092967d66dff08" [metadata.files] aiofile = [ @@ -1208,6 +1216,10 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] +nanoid = [ + {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, + {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, +] numpy = [ {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, diff --git a/pyproject.toml b/pyproject.toml index a379526..3c6707a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ rook = "^0.1.170" rich = "^12.3.0" jurigged = "^0.5.0" aioredis = "^2.0.1" +nanoid = "^2.0.0" [build-system] requires = ["poetry-core>=1.0.0"] From 1813c1e4b006c9858cecf7a0fedd79abe5d010e0 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 14:36:03 -0600 Subject: [PATCH 333/365] Move nanoid to core, utilize it for moderation cases --- jarvis/cogs/admin/modcase.py | 2 +- poetry.lock | 20 ++++---------------- pyproject.toml | 1 - 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 52faccf..1d2f702 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -119,7 +119,7 @@ class CaseCog(Cog): ) embed = build_embed( - title=f"Moderation Case [{mod_case.id}]", + title=f"Moderation Case [{mod_case.nanoid}]", description=f"{status} case against {user_text} [**opened by {admin_text}**]", fields=fields, timestamp=mod_case.created_at, diff --git a/poetry.lock b/poetry.lock index 172c2a4..4e67a9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -407,14 +407,6 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "nanoid" -version = "2.0.0" -description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "numpy" version = "1.22.3" @@ -491,11 +483,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} +idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -887,7 +879,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "a0e99de0ed0905f03abc1d97775dfc7dfb93fe5956a687f7d2092967d66dff08" +content-hash = "ddb58df08ef6eb999fc99db2b785fe6f6daa641aaf451daba6d8be1708ba7e3d" [metadata.files] aiofile = [ @@ -1216,10 +1208,6 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -nanoid = [ - {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, - {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, -] numpy = [ {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, diff --git a/pyproject.toml b/pyproject.toml index 3c6707a..a379526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ rook = "^0.1.170" rich = "^12.3.0" jurigged = "^0.5.0" aioredis = "^2.0.1" -nanoid = "^2.0.0" [build-system] requires = ["poetry-core>=1.0.0"] From d89e3ea21a3eabaed9c8cf05043b12a1d5c82251 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 14:54:20 -0600 Subject: [PATCH 334/365] Add case show and case close --- jarvis/cogs/admin/modcase.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 1d2f702..d183789 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -119,7 +119,7 @@ class CaseCog(Cog): ) embed = build_embed( - title=f"Moderation Case [{mod_case.nanoid}]", + title=f"Moderation Case [`{mod_case.nanoid}`]", description=f"{status} case against {user_text} [**opened by {admin_text}**]", fields=fields, timestamp=mod_case.created_at, @@ -167,3 +167,32 @@ class CaseCog(Cog): pages = [await self.get_embed(c, ctx.guild) for c in cases] paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) await paginator.send(ctx) + + case = SlashCommand(name="case", description="Manage a moderation case") + + @case.subcommand(sub_cmd_name="show", sub_cmd_description="Show a specific case") + @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_show(self, ctx: InteractionContext, cid: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) + if not case: + await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) + return + + embed = await self.get_embed(case, ctx.guild) + await ctx.send(embed=embed) + + @case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case") + @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_close(self, ctx: InteractionContext, cid: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) + if not case: + await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) + return + + case.open = False + await case.commit() + + embed = await self.get_embed(case, ctx.guild) + await ctx.send(embed=embed) From 7968dd7780c55d161a3dd56c858efc415fdd79b8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 14:55:42 -0600 Subject: [PATCH 335/365] Fix cog reloading --- jarvis/utils/updates.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 3f556f7..7e896f1 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -157,10 +157,10 @@ async def update(bot: "Client") -> Optional[UpdateResult]: for module, commands in new_commands.items(): logger.debug("Processing %s", module) if module not in current_commands: - bot.grow_cog(module) + bot.load_cog(module) loaded.append(module) elif len(current_commands[module]) != len(commands): - bot.regrow_cog(module) + bot.reload_cog(module) reloaded.append(module) else: for command in commands: @@ -182,15 +182,15 @@ async def update(bot: "Client") -> Optional[UpdateResult]: # Check if number arguments have changed if len(old_args) != len(new_args): - bot.regrow_cog(module) + bot.reload_cog(module) reloaded.append(module) elif any(x not in old_arg_names for x in new_arg_names) or any( x not in new_arg_names for x in old_arg_names ): - bot.regrow_cog(module) + bot.reload_cog(module) reloaded.append(module) elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - bot.regrow_cog(module) + bot.reload_cog(module) reloaded.append(module) return UpdateResult( From 3ea435aaa36f8acec2c2ce3b8ac4e9a0a576ecc8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 15:27:02 -0600 Subject: [PATCH 336/365] Change structure of case viewing, add actions view --- jarvis/cogs/admin/modcase.py | 87 ++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index d183789..071da4a 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -1,5 +1,5 @@ """JARVIS Moderation Case management.""" -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, List, Optional from jarvis_core.db import q from jarvis_core.db.models import Modlog, actions @@ -34,9 +34,9 @@ ACTIONS_LOOKUP = { class CaseCog(Cog): """JARVIS CaseCog.""" - async def get_embed(self, mod_case: Modlog, guild: "Guild") -> Embed: + async def get_summary_embed(self, mod_case: Modlog, guild: "Guild") -> Embed: """ - Get Moderation case embed. + Get Moderation case summary embed. Args: mod_case: Moderation case @@ -59,10 +59,10 @@ class CaseCog(Cog): parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) if not parent_action: action.orphaned = True - action_table.add_row(action.action_type.title(), "N/A", "N/A") + action_table.add_row(action.action_type.title(), "[N/A]", "[N/A]") else: admin = await self.bot.fetch_user(parent_action.admin) - admin_text = "Deleted User" + admin_text = "[N/A]" if admin: admin_text = f"{admin.username}#{admin.discriminator}" action_table.add_row(action.action_type.title(), admin_text, parent_action.reason) @@ -81,7 +81,7 @@ class CaseCog(Cog): notes = sorted(mod_case.notes, key=lambda x: x.created_at) for idx, note in enumerate(notes): admin = await self.bot.fetch_user(note.admin) - admin_text = "N/A" + admin_text = "[N/A]" if admin: admin_text = f"{admin.username}#{admin.discriminator}" note_table.add_row(admin_text, note.content) @@ -99,8 +99,8 @@ class CaseCog(Cog): status = "Open" if mod_case.open else "Closed" user = await self.bot.fetch_user(mod_case.user) - username = "Deleted User" - user_text = "Deleted User" + username = "[N/A]" + user_text = "[N/A]" if user: username = f"{user.username}#{user.discriminator}" user_text = user.mention @@ -134,6 +134,53 @@ class CaseCog(Cog): await mod_case.commit() return embed + async def get_action_embeds(self, mod_case: Modlog, guild: "Guild") -> List[Embed]: + """ + Get Moderation case action embeds. + + Args: + mod_case: Moderation case + guild: Originating guild + """ + embeds = [] + user = await self.bot.fetch_user(mod_case.user) + username = "[N/A]" + user_mention = "[N/A]" + avatar_url = None + if user: + username = f"{user.username}#{user.discriminator}" + avatar_url = user.avatar.url + user_mention = user.mention + + for action in mod_case.actions: + if action.orphaned: + continue + parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) + if not parent_action: + action.orphaned = True + continue + + admin = await self.bot.fetch_user(parent_action.admin) + admin_text = "[N/A]" + if admin: + admin_text = admin.mention + + fields = ( + EmbedField(name="Admin", value=admin_text), + EmbedField(name="Reason", value=parent_action.reason), + ) + embed = build_embed( + title="Moderation Case Action", + description=f"{user_mention} was warned by {admin_text}", + fields=fields, + timestamp=parent_action.created_at, + ) + embed.set_author(name=username, icon_url=avatar_url) + embeds.append(embed) + + await mod_case.commit() + return embeds + cases = SlashCommand(name="cases", description="Manage moderation cases") @cases.subcommand(sub_cmd_name="list", sub_cmd_description="List moderation cases") @@ -164,24 +211,38 @@ class CaseCog(Cog): await ctx.send("No cases to view", ephemeral=True) return - pages = [await self.get_embed(c, ctx.guild) for c in cases] + pages = [await self.get_summary_embed(c, ctx.guild) for c in cases] paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) await paginator.send(ctx) case = SlashCommand(name="case", description="Manage a moderation case") + show = case.group(name="show", description="Show information about a specific case") - @case.subcommand(sub_cmd_name="show", sub_cmd_description="Show a specific case") + @show.subcommand(sub_cmd_name="summary", sub_cmd_description="Summarize a specific case") @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) - async def _case_show(self, ctx: InteractionContext, cid: str) -> None: + async def _case_show_summary(self, ctx: InteractionContext, cid: str) -> None: case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) if not case: await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) return - embed = await self.get_embed(case, ctx.guild) + embed = await self.get_summary_embed(case, ctx.guild) await ctx.send(embed=embed) + @show.subcommand(sub_cmd_name="actions", description="Get case actions") + @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) + if not case: + await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) + return + + pages = await self.get_action_embeds(case, ctx.guild) + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) + await paginator.send(ctx) + @case.subcommand(sub_cmd_name="close", sub_cmd_description="Show a specific case") @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) @@ -194,5 +255,5 @@ class CaseCog(Cog): case.open = False await case.commit() - embed = await self.get_embed(case, ctx.guild) + embed = await self.get_summary_embed(case, ctx.guild) await ctx.send(embed=embed) From b665fa45b8fd86c8afcddd6e4ddd258f4c6312e3 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 15:27:45 -0600 Subject: [PATCH 337/365] Fix wrong keyword --- jarvis/cogs/admin/modcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 071da4a..ceadaae 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -230,7 +230,7 @@ class CaseCog(Cog): embed = await self.get_summary_embed(case, ctx.guild) await ctx.send(embed=embed) - @show.subcommand(sub_cmd_name="actions", description="Get case actions") + @show.subcommand(sub_cmd_name="actions", sub_cmd_description="Get case actions") @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _case_show_actions(self, ctx: InteractionContext, cid: str) -> None: From be1618c782c4a18d6cb1b98753c379c52013d5f5 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 15:30:44 -0600 Subject: [PATCH 338/365] Change embed format --- jarvis/cogs/admin/modcase.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index ceadaae..2f076cc 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -165,13 +165,10 @@ class CaseCog(Cog): if admin: admin_text = admin.mention - fields = ( - EmbedField(name="Admin", value=admin_text), - EmbedField(name="Reason", value=parent_action.reason), - ) + fields = (EmbedField(name=action.action_type.title(), value=parent_action.reason),) embed = build_embed( title="Moderation Case Action", - description=f"{user_mention} was warned by {admin_text}", + description=f"{admin_text} initiated an action against {user_mention}", fields=fields, timestamp=parent_action.created_at, ) From 853dbdb80b62d629fde25f637b9b21a2fd4a4cc8 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 15:59:18 -0600 Subject: [PATCH 339/365] Add more case commands --- jarvis/client.py | 3 +- jarvis/cogs/admin/modcase.py | 73 +++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 297314a..69bc0f9 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -651,7 +651,8 @@ class Jarvis(Client): async def on_button(self, event: Button) -> None: """Process button events.""" context = event.context - await context.defer(ephemeral=True) + if not context.deferred and not context.responded: + await context.defer(ephemeral=True) if not context.custom_id.startswith("modcase|"): return await super().on_button(event) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 2f076cc..a45ead9 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, List, Optional from jarvis_core.db import q -from jarvis_core.db.models import Modlog, actions +from jarvis_core.db.models import Modlog, Note, actions from naff import Cog, InteractionContext, Permissions from naff.ext.paginators import Paginator from naff.models.discord.embed import Embed, EmbedField @@ -254,3 +254,74 @@ class CaseCog(Cog): embed = await self.get_summary_embed(case, ctx.guild) await ctx.send(embed=embed) + + @case.subcommand(sub_cmd_name="repoen", sub_cmd_description="Reopen a specific case") + @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_reopen(self, ctx: InteractionContext, cid: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) + if not case: + await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) + return + + case.open = True + await case.commit() + + embed = await self.get_summary_embed(case, ctx.guild) + await ctx.send(embed=embed) + + @case.subcommand(sub_cmd_name="note", sub_cmd_description="Add a note to a specific case") + @slash_option(name="cid", description="Case ID", opt_type=OptionTypes.STRING, required=True) + @slash_option( + name="note", description="Note to add", opt_type=OptionTypes.STRING, required=True + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_note(self, ctx: InteractionContext, cid: str, note: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, nanoid=cid)) + if not case: + await ctx.send(f"Could not find case with ID {cid}", ephemeral=True) + return + + if not case.open: + await ctx.send("Case is closed, please re-open to add a new comment", ephemeral=True) + return + + if len(note) > 50: + await ctx.send("Note must be <= 50 characters", ephemeral=True) + return + + note = Note(admin=ctx.author.id, content=note) + + case.notes.append(note) + await case.commit() + + embed = await self.get_summary_embed(case, ctx.guild) + await ctx.send(embed=embed) + + @case.subcommand(sub_cmd_name="new", sub_cmd_description="Open a new case") + @slash_option(name="user", description="Target user", opt_type=OptionTypes.USER, required=True) + @slash_option( + name="note", description="Note to add", opt_type=OptionTypes.STRING, required=True + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None: + case = await Modlog.find_one(q(guild=ctx.guild.id, user=user.id, open=True)) + if not case: + await ctx.send(f"Case already exists with ID {case.nanoid}", ephemeral=True) + return + + if not isinstance(user, Member): + await ctx.send("User must be in this guild", ephemeral=True) + return + + if len(note) > 50: + await ctx.send("Note must be <= 50 characters", ephemeral=True) + return + + note = Note(admin=ctx.author.id, content=note) + + case = Modlog(user=user.id, guild=ctx.guild.id, admin=ctx.author.id, notes=[note]) + await case.commit() + + embed = await self.get_summary_embed(case, ctx.guild) + await ctx.send(embed=embed) From 44579329afdff9fd9d6d0e08b80a53d90ec9270a Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:01:05 -0600 Subject: [PATCH 340/365] Handle extensions not being loaded on reload (needs more debugging) --- jarvis/utils/updates.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index 7e896f1..c7ee5a8 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -9,6 +9,7 @@ from types import FunctionType, ModuleType from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional import git +from naff.client.errors import ExtensionNotFound from naff.client.utils.misc_utils import find, find_all from naff.models.naff.application_commands import SlashCommand from naff.models.naff.cog import Cog @@ -160,7 +161,10 @@ async def update(bot: "Client") -> Optional[UpdateResult]: bot.load_cog(module) loaded.append(module) elif len(current_commands[module]) != len(commands): - bot.reload_cog(module) + try: + bot.reload_cog(module) + except ExtensionNotFound: + bot.load_cog(module) reloaded.append(module) else: for command in commands: @@ -182,15 +186,24 @@ async def update(bot: "Client") -> Optional[UpdateResult]: # Check if number arguments have changed if len(old_args) != len(new_args): - bot.reload_cog(module) + try: + bot.reload_cog(module) + except ExtensionNotFound: + bot.load_cog(module) reloaded.append(module) elif any(x not in old_arg_names for x in new_arg_names) or any( x not in new_arg_names for x in old_arg_names ): - bot.reload_cog(module) + try: + bot.reload_cog(module) + except ExtensionNotFound: + bot.load_cog(module) reloaded.append(module) elif any(new_args[idx].type != x.type for idx, x in enumerate(old_args)): - bot.reload_cog(module) + try: + bot.reload_cog(module) + except ExtensionNotFound: + bot.load_cog(module) reloaded.append(module) return UpdateResult( From 5dd3c9548cd665491fa67c62f7b7d527354e0b83 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:09:03 -0600 Subject: [PATCH 341/365] Fix case new logic --- jarvis/cogs/admin/modcase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index a45ead9..581fd03 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -306,8 +306,8 @@ class CaseCog(Cog): @check(admin_or_permissions(Permissions.BAN_MEMBERS)) async def _case_new(self, ctx: InteractionContext, user: Member, note: str) -> None: case = await Modlog.find_one(q(guild=ctx.guild.id, user=user.id, open=True)) - if not case: - await ctx.send(f"Case already exists with ID {case.nanoid}", ephemeral=True) + if case: + await ctx.send(f"Case already open with ID `{case.nanoid}`", ephemeral=True) return if not isinstance(user, Member): From d2a8080c3a5b6caf463ff477ea6ef1287374e25c Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:20:27 -0600 Subject: [PATCH 342/365] Reload case after it's created --- jarvis/cogs/admin/modcase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index 581fd03..d8234d5 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -322,6 +322,7 @@ class CaseCog(Cog): case = Modlog(user=user.id, guild=ctx.guild.id, admin=ctx.author.id, notes=[note]) await case.commit() + await case.reload() embed = await self.get_summary_embed(case, ctx.guild) await ctx.send(embed=embed) From a35561d5673971071f9fd33fec69f7b67fe113d1 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:22:12 -0600 Subject: [PATCH 343/365] Fix nonetype error on embed summary --- jarvis/cogs/admin/modcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index d8234d5..c91e8fd 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -55,7 +55,7 @@ class CaseCog(Cog): action_output = "" action_output_extra = "" - for idx, action in enumerate(mod_case.actions): + for idx, action in enumerate(mod_case.actions or []): parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) if not parent_action: action.orphaned = True From 714fd36fef7121433b5c943e8432d18a4c87255b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:24:07 -0600 Subject: [PATCH 344/365] Change formatting to account for missing fields --- jarvis/cogs/admin/modcase.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index c91e8fd..f88366b 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -78,7 +78,7 @@ class CaseCog(Cog): note_output = "" note_output_extra = "" - notes = sorted(mod_case.notes, key=lambda x: x.created_at) + notes = sorted(mod_case.notes or [], key=lambda x: x.created_at) for idx, note in enumerate(notes): admin = await self.bot.fetch_user(note.admin) admin_text = "[N/A]" @@ -114,8 +114,10 @@ class CaseCog(Cog): note_output = f"```ansi\n{note_output}\n{note_output_extra}\n```" fields = ( - EmbedField(name="Actions", value=action_output), - EmbedField(name="Notes", value=note_output), + EmbedField( + name="Actions", value=action_output if mod_case.actions else "No Actions Found" + ), + EmbedField(name="Notes", value=note_output if mod_case.notes else "No Notes Found"), ) embed = build_embed( From 691285a78e184e5cdf7aa64c310efeae161a8523 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 3 May 2022 16:25:37 -0600 Subject: [PATCH 345/365] No longer assume fields are empty --- jarvis/cogs/admin/modcase.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py index f88366b..3cc446f 100644 --- a/jarvis/cogs/admin/modcase.py +++ b/jarvis/cogs/admin/modcase.py @@ -55,7 +55,7 @@ class CaseCog(Cog): action_output = "" action_output_extra = "" - for idx, action in enumerate(mod_case.actions or []): + for idx, action in enumerate(mod_case.actions): parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) if not parent_action: action.orphaned = True @@ -78,7 +78,7 @@ class CaseCog(Cog): note_output = "" note_output_extra = "" - notes = sorted(mod_case.notes or [], key=lambda x: x.created_at) + notes = sorted(mod_case.notes, key=lambda x: x.created_at) for idx, note in enumerate(notes): admin = await self.bot.fetch_user(note.admin) admin_text = "[N/A]" @@ -322,7 +322,9 @@ class CaseCog(Cog): note = Note(admin=ctx.author.id, content=note) - case = Modlog(user=user.id, guild=ctx.guild.id, admin=ctx.author.id, notes=[note]) + case = Modlog( + user=user.id, guild=ctx.guild.id, admin=ctx.author.id, notes=[note], actions=[] + ) await case.commit() await case.reload() From 9f374f0b1aa0d95f14a207396813b51c6df13628 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 21:29:31 -0600 Subject: [PATCH 346/365] Fix invalid notification on some commands --- jarvis/utils/cogs.py | 2 +- poetry.lock | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/jarvis/utils/cogs.py b/jarvis/utils/cogs.py index 25a07b6..c18ac38 100644 --- a/jarvis/utils/cogs.py +++ b/jarvis/utils/cogs.py @@ -31,7 +31,7 @@ class ModcaseCog(Cog): """ name = self.__name__.replace("Cog", "") - if name in MODLOG_LOOKUP and ctx.command not in IGNORE_COMMANDS[name]: + if name in MODLOG_LOOKUP and ctx.invoke_target not in IGNORE_COMMANDS[name]: user = kwargs.pop("user", None) if not user and not ctx.target_id: self.logger.warning("Admin action %s missing user, exiting", name) diff --git a/poetry.lock b/poetry.lock index 4e67a9a..2697614 100644 --- a/poetry.lock +++ b/poetry.lock @@ -307,7 +307,7 @@ python-versions = ">=3.5" [[package]] name = "jarvis-core" -version = "0.9.1" +version = "0.9.2" description = "JARVIS core" category = "main" optional = false @@ -317,6 +317,7 @@ develop = false [package.dependencies] aiohttp = "^3.8.1" motor = "^2.5.1" +nanoid = "^2.0.0" orjson = "^3.6.6" pytz = "^2022.1" PyYAML = "^6.0" @@ -327,7 +328,7 @@ umongo = "^3.1.0" type = "git" url = "https://git.zevaryx.com/stark-industries/jarvis/jarvis-core.git" reference = "main" -resolved_reference = "aa13a556d67e926aca6e3053aa383d9cdf5a88b1" +resolved_reference = "83117c1b3c5540acadeac3005f4d8e69cbf743fc" [[package]] name = "jinxed" @@ -407,6 +408,14 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "nanoid" +version = "2.0.0" +description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "numpy" version = "1.22.3" @@ -483,11 +492,11 @@ python-versions = ">=3.10" aiohttp = {version = "3.8.1", markers = "python_version >= \"3.6\""} aiosignal = {version = "1.2.0", markers = "python_version >= \"3.6\""} async-timeout = {version = "4.0.2", markers = "python_version >= \"3.6\""} -attrs = {version = "21.4.0", markers = "python_version >= \"3.6\""} +attrs = {version = "21.4.0", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\" and python_version >= \"3.6\""} certifi = {version = "2021.10.8", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} -charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\""} +charset-normalizer = {version = "2.0.12", markers = "python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} frozenlist = {version = "1.3.0", markers = "python_version >= \"3.7\""} -idna = {version = "3.3", markers = "python_full_version >= \"3.6.0\""} +idna = {version = "3.3", markers = "python_version >= \"3.6\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\" and python_version >= \"3.6\""} multidict = {version = "6.0.2", markers = "python_version >= \"3.7\""} pycryptodome = {version = "3.14.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.5.0\""} requests = {version = "2.27.1", markers = "python_version >= \"2.7\" and python_full_version < \"3.0.0\" or python_full_version >= \"3.6.0\""} @@ -879,7 +888,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "ddb58df08ef6eb999fc99db2b785fe6f6daa641aaf451daba6d8be1708ba7e3d" +content-hash = "8bb2b59de1ccb8f5e5588ae3ac600e7fb6d7f638224c9cc24228f79e666aec63" [metadata.files] aiofile = [ @@ -1208,6 +1217,10 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] +nanoid = [ + {file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"}, + {file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"}, +] numpy = [ {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, From 0d22b4bb33acd8dae3d1d93f01497840e1b2a470 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 22:53:12 -0600 Subject: [PATCH 347/365] Process member events, closes #138 --- jarvis/client.py | 107 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 69bc0f9..acd523e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -22,17 +22,19 @@ from naff import Client, listen from naff.api.events.discord import ( MemberAdd, MemberRemove, + MemberUpdate, MessageCreate, MessageDelete, MessageUpdate, ) from naff.api.events.internal import Button from naff.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException -from naff.client.utils.misc_utils import find_all -from naff.models.discord.channel import DMChannel -from naff.models.discord.embed import EmbedField -from naff.models.discord.enums import Permissions +from naff.client.utils.misc_utils import find, find_all, get +from naff.models.discord.channel import DMChannel, GuildText +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.enums import AuditLogEventType, Permissions from naff.models.discord.message import Message +from naff.models.discord.user import Member from naff.models.naff.context import Context, InteractionContext, PrefixedContext from naff.models.naff.tasks.task import Task from naff.models.naff.tasks.triggers import IntervalTrigger @@ -291,6 +293,103 @@ class Jarvis(Client): embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await channel.send(embed=embed) + async def process_verify(self, before: Member, after: Member) -> Embed: + """Process user verification.""" + auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + audit_event = get(auditlog.events, reason="Verification passed") + if audit_event: + admin_mention = "[N/A]" + admin_text = "[N/A]" + if admin := await after.guild.fet_member(audit_event.user_id): + admin_mention = admin.mention + admin_text = f"{admin.username}#{admin.discriminator}" + fields = ( + EmbedField(name="Moderator", value=f"{admin_mention} ({admin_text})"), + EmbedField(name="Reason", value=audit_event.reason) + ) + embed = build_embed(title="User Verified", description=f"{after.mention} was verified", fields=fields, color="#fc9e3f") + embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) + embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") + return embed + + async def process_rolechange(self, before: Member, after: Member) -> Embed: + """Process role changes.""" + if before.roles == after.roles: + return + + auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + new_roles = {} + removed_roles = {} + + for role in before.roles: + if role not in after.roles: + reason = "N/A" + if entry := find(lambda x: x.new_value == role.id, auditlog.entires): + reason = entry.reason + removed_roles[role] = reason + for role in after.roles: + if role not in before.roles: + reason = "N/A" + if entry := find(lambda x: x.new_value == role.id, auditlog.entires): + reason = entry.reason + new_roles[role] = reason + + new_text = "\n".join(f"k [v]" for k, v in new_roles.items()) or "None" + removed_text = "\n".join(f"k [v]" for k, v in removed_roles.items()) or "None" + + fields = ( + EmbedField(name="Added Roles", value=new_text), + EmbedField(name="Removed Roles", value=removed_text), + ) + embed = build_embed(title="User Roles Changed", description=f"{after.mention} had roles changed", fields=fields, color="#fc9e3f") + embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) + embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") + return embed + + async def process_rename(self, before: Member, after: member) -> None: + """Process name change.""" + if before.nickname == after.nickname and before.discriminator == after.discriminator and before.username == after.username: + return + + fields = ( + EmbedField(name="Before", value=f"{before.display_name} ({before.username}#{before.discriminator})"), + EmbedField(name="After", value=f"{after.display_name} ({after.username}#{after.discriminator})"), + ) + embed = build_embed(title="User Renamed", description=f"{after.mention} changed their name", fields=fields, color="#fc9e3f") + embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) + embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") + return embed + + @listen() + async def on_member_update(self, event: MemberUpdate) -> None: + """Handle on_member_update event.""" + before = event.before + after = event.after + + if (before.display_name == after.display_name and before.roles == after.roles) or (not after or not before): + return + + log = await Setting.find_one(q(guild=guild.id, setting="activitylog")) + if log: + channel = await before.guild.fetch_channel(log.value) + await asyncio.sleep(0.5) # Wait for audit log + embed = None + if before._role_ids != after._role_ids: + verified = await Settings.find_one(q(guild=before.guild.id, setting="verified")) + v_role = None + if verified: + v_role = await before.guild.fetch_role(verified.value) + if not v_role: + self.logger.debug(f"Guild {before.guild.id} verified role no longer exists") + await verified.delete() + else: + if not before.has_role(v_role) and after.has_role(v_role): + embed = await self.process_verify(before, after) + if not embed: + embed = self.process_rolechange(before, after) or self.process_rename(before, after) + if embed: + await channel.send(embed=embed) + # Message async def autopurge(self, message: Message) -> None: """Handle autopurge events.""" From d647c8627cc075232dea3bcd9239875f03adedf5 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 22:54:25 -0600 Subject: [PATCH 348/365] Fix typo --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index acd523e..f33114e 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -346,7 +346,7 @@ class Jarvis(Client): embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") return embed - async def process_rename(self, before: Member, after: member) -> None: + async def process_rename(self, before: Member, after: Member) -> None: """Process name change.""" if before.nickname == after.nickname and before.discriminator == after.discriminator and before.username == after.username: return From 3c7b9e0f81b9eaeb3ec064ac9583fbca80d59226 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 22:56:37 -0600 Subject: [PATCH 349/365] Catch phishing sync failure --- jarvis/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index f33114e..a4693f4 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -122,8 +122,11 @@ class Jarvis(Client): @listen() async def on_ready(self) -> None: """NAFF on_ready override.""" - await self._sync_domains() - self._update_domains.start() + try: + await self._sync_domains() + self._update_domains.start() + except Exception as e: + self.logger.error("Failed to load anti-phishing", exc_info=e) self.logger.info("Logged in as {}".format(self.user)) # noqa: T001 self.logger.info("Connected to {} guild(s)".format(len(self.guilds))) # noqa: T001 self.logger.info("Current version: {}".format(const.__version__)) From 1c36fdf3b87716c805855672982f7275596407a1 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:03:15 -0600 Subject: [PATCH 350/365] Fix missing guild field --- jarvis/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index a4693f4..e1fea03 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -298,7 +298,8 @@ class Jarvis(Client): async def process_verify(self, before: Member, after: Member) -> Embed: """Process user verification.""" - auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + guild = await self.fetch_guild(after._guild_id) + auditlog = await guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) audit_event = get(auditlog.events, reason="Verification passed") if audit_event: admin_mention = "[N/A]" @@ -320,7 +321,8 @@ class Jarvis(Client): if before.roles == after.roles: return - auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + guild = await self.fetch_guild(after._guild_id) + auditlog = await guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) new_roles = {} removed_roles = {} From c47d0373d42df1a5543bf2730950c6c12801fcae Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:04:55 -0600 Subject: [PATCH 351/365] Fix activity log lookup --- jarvis/client.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index e1fea03..b364365 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -298,8 +298,7 @@ class Jarvis(Client): async def process_verify(self, before: Member, after: Member) -> Embed: """Process user verification.""" - guild = await self.fetch_guild(after._guild_id) - auditlog = await guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) audit_event = get(auditlog.events, reason="Verification passed") if audit_event: admin_mention = "[N/A]" @@ -321,8 +320,7 @@ class Jarvis(Client): if before.roles == after.roles: return - guild = await self.fetch_guild(after._guild_id) - auditlog = await guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) new_roles = {} removed_roles = {} @@ -374,7 +372,7 @@ class Jarvis(Client): if (before.display_name == after.display_name and before.roles == after.roles) or (not after or not before): return - log = await Setting.find_one(q(guild=guild.id, setting="activitylog")) + log = await Setting.find_one(q(guild=before.guild.id, setting="activitylog")) if log: channel = await before.guild.fetch_channel(log.value) await asyncio.sleep(0.5) # Wait for audit log From d0ec406e1eaf55a3afc971b68169a85489e8e959 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:08:37 -0600 Subject: [PATCH 352/365] Fix various issues --- jarvis/client.py | 65 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index b364365..c0464dd 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -1,4 +1,5 @@ """Custom JARVIS client.""" +import asyncio import logging import re import traceback @@ -30,7 +31,7 @@ from naff.api.events.discord import ( from naff.api.events.internal import Button from naff.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException from naff.client.utils.misc_utils import find, find_all, get -from naff.models.discord.channel import DMChannel, GuildText +from naff.models.discord.channel import DMChannel from naff.models.discord.embed import Embed, EmbedField from naff.models.discord.enums import AuditLogEventType, Permissions from naff.models.discord.message import Message @@ -298,7 +299,9 @@ class Jarvis(Client): async def process_verify(self, before: Member, after: Member) -> Embed: """Process user verification.""" - auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + auditlog = await after.guild.fetch_audit_log( + user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE + ) audit_event = get(auditlog.events, reason="Verification passed") if audit_event: admin_mention = "[N/A]" @@ -308,9 +311,14 @@ class Jarvis(Client): admin_text = f"{admin.username}#{admin.discriminator}" fields = ( EmbedField(name="Moderator", value=f"{admin_mention} ({admin_text})"), - EmbedField(name="Reason", value=audit_event.reason) + EmbedField(name="Reason", value=audit_event.reason), + ) + embed = build_embed( + title="User Verified", + description=f"{after.mention} was verified", + fields=fields, + color="#fc9e3f", ) - embed = build_embed(title="User Verified", description=f"{after.mention} was verified", fields=fields, color="#fc9e3f") embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") return embed @@ -320,7 +328,9 @@ class Jarvis(Client): if before.roles == after.roles: return - auditlog = await after.guild.fetch_audit_log(user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE) + auditlog = await after.guild.fetch_audit_log( + user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE + ) new_roles = {} removed_roles = {} @@ -337,28 +347,47 @@ class Jarvis(Client): reason = entry.reason new_roles[role] = reason - new_text = "\n".join(f"k [v]" for k, v in new_roles.items()) or "None" - removed_text = "\n".join(f"k [v]" for k, v in removed_roles.items()) or "None" + new_text = "\n".join(f"{k} [{v}]" for k, v in new_roles.items()) or "None" + removed_text = "\n".join(f"{k} [{v}]" for k, v in removed_roles.items()) or "None" fields = ( EmbedField(name="Added Roles", value=new_text), EmbedField(name="Removed Roles", value=removed_text), ) - embed = build_embed(title="User Roles Changed", description=f"{after.mention} had roles changed", fields=fields, color="#fc9e3f") + embed = build_embed( + title="User Roles Changed", + description=f"{after.mention} had roles changed", + fields=fields, + color="#fc9e3f", + ) embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") return embed async def process_rename(self, before: Member, after: Member) -> None: """Process name change.""" - if before.nickname == after.nickname and before.discriminator == after.discriminator and before.username == after.username: + if ( + before.nickname == after.nickname + and before.discriminator == after.discriminator + and before.username == after.username + ): return - + fields = ( - EmbedField(name="Before", value=f"{before.display_name} ({before.username}#{before.discriminator})"), - EmbedField(name="After", value=f"{after.display_name} ({after.username}#{after.discriminator})"), + EmbedField( + name="Before", + value=f"{before.display_name} ({before.username}#{before.discriminator})", + ), + EmbedField( + name="After", value=f"{after.display_name} ({after.username}#{after.discriminator})" + ), + ) + embed = build_embed( + title="User Renamed", + description=f"{after.mention} changed their name", + fields=fields, + color="#fc9e3f", ) - embed = build_embed(title="User Renamed", description=f"{after.mention} changed their name", fields=fields, color="#fc9e3f") embed.set_author(name=after.display_name, icon_url=after.display_avatar.url) embed.set_footer(text=f"{after.username}#{after.discriminator} | {after.id}") return embed @@ -369,7 +398,9 @@ class Jarvis(Client): before = event.before after = event.after - if (before.display_name == after.display_name and before.roles == after.roles) or (not after or not before): + if (before.display_name == after.display_name and before.roles == after.roles) or ( + not after or not before + ): return log = await Setting.find_one(q(guild=before.guild.id, setting="activitylog")) @@ -378,7 +409,7 @@ class Jarvis(Client): await asyncio.sleep(0.5) # Wait for audit log embed = None if before._role_ids != after._role_ids: - verified = await Settings.find_one(q(guild=before.guild.id, setting="verified")) + verified = await Setting.find_one(q(guild=before.guild.id, setting="verified")) v_role = None if verified: v_role = await before.guild.fetch_role(verified.value) @@ -389,7 +420,9 @@ class Jarvis(Client): if not before.has_role(v_role) and after.has_role(v_role): embed = await self.process_verify(before, after) if not embed: - embed = self.process_rolechange(before, after) or self.process_rename(before, after) + embed = self.process_rolechange(before, after) or self.process_rename( + before, after + ) if embed: await channel.send(embed=embed) From 1e854656671875a7dad149c56252a32c8e815b8e Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:11:27 -0600 Subject: [PATCH 353/365] Await processing --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index c0464dd..6952f1c 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -420,9 +420,9 @@ class Jarvis(Client): if not before.has_role(v_role) and after.has_role(v_role): embed = await self.process_verify(before, after) if not embed: - embed = self.process_rolechange(before, after) or self.process_rename( + embed = await self.process_rolechange( before, after - ) + ) or await self.process_rename(before, after) if embed: await channel.send(embed=embed) From 2f436d3651ddcc10dcb60f5dd740043d357d68a0 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:16:02 -0600 Subject: [PATCH 354/365] Fix typo --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index 6952f1c..7b84d72 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -343,7 +343,7 @@ class Jarvis(Client): for role in after.roles: if role not in before.roles: reason = "N/A" - if entry := find(lambda x: x.new_value == role.id, auditlog.entires): + if entry := find(lambda x: x.new_value == role.id, auditlog.entries): reason = entry.reason new_roles[role] = reason From 7bf5cc8cb3a233e68528b62ddc8d70b6f549898d Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:45:52 -0600 Subject: [PATCH 355/365] Simplify logic for member_update --- jarvis/client.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 7b84d72..f532642 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -419,12 +419,10 @@ class Jarvis(Client): else: if not before.has_role(v_role) and after.has_role(v_role): embed = await self.process_verify(before, after) - if not embed: - embed = await self.process_rolechange( - before, after - ) or await self.process_rename(before, after) - if embed: - await channel.send(embed=embed) + embed = embed or await self.process_rolechange(before, after) + embed = embed or await self.process_rename(before, after) + if embed: + await channel.send(embed=embed) # Message async def autopurge(self, message: Message) -> None: From 9695074b3e5097f2c2d413a55296e27db02784d5 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:47:07 -0600 Subject: [PATCH 356/365] Fix typo --- jarvis/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/client.py b/jarvis/client.py index f532642..5265e34 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -337,7 +337,7 @@ class Jarvis(Client): for role in before.roles: if role not in after.roles: reason = "N/A" - if entry := find(lambda x: x.new_value == role.id, auditlog.entires): + if entry := find(lambda x: x.new_value == role.id, auditlog.entries): reason = entry.reason removed_roles[role] = reason for role in after.roles: From d32afb6fc1d611d6805709abae4233011dd817c3 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:49:49 -0600 Subject: [PATCH 357/365] Change logic to look through entry changes --- jarvis/client.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 5265e34..3bf3805 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -337,14 +337,18 @@ class Jarvis(Client): for role in before.roles: if role not in after.roles: reason = "N/A" - if entry := find(lambda x: x.new_value == role.id, auditlog.entries): - reason = entry.reason + for entry in auditlog.entries: + if find(lambda x: x.new_value == role.id, entry.changes): + reason = entry.reason + break removed_roles[role] = reason for role in after.roles: if role not in before.roles: reason = "N/A" - if entry := find(lambda x: x.new_value == role.id, auditlog.entries): - reason = entry.reason + for entry in auditlog.entries: + if find(lambda x: x.new_value == role.id, entry.changes): + reason = entry.reason + break new_roles[role] = reason new_text = "\n".join(f"{k} [{v}]" for k, v in new_roles.items()) or "None" From 7ccc7ea6d2c3d77dde658dde06cc7ee9f55ac470 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:51:05 -0600 Subject: [PATCH 358/365] Fix output of member update --- jarvis/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 3bf3805..1e031fd 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -351,8 +351,8 @@ class Jarvis(Client): break new_roles[role] = reason - new_text = "\n".join(f"{k} [{v}]" for k, v in new_roles.items()) or "None" - removed_text = "\n".join(f"{k} [{v}]" for k, v in removed_roles.items()) or "None" + new_text = "\n".join(f"{k.mention} [{v}]" for k, v in new_roles.items()) or "None" + removed_text = "\n".join(f"{k.mention} [{v}]" for k, v in removed_roles.items()) or "None" fields = ( EmbedField(name="Added Roles", value=new_text), From ab71fa8b3c8678248d1ed2eb90dea90966a1a41e Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:53:49 -0600 Subject: [PATCH 359/365] Add check to rolegiver to verify that roles property exists --- jarvis/cogs/rolegiver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jarvis/cogs/rolegiver.py b/jarvis/cogs/rolegiver.py index 01fcc8a..0200a6e 100644 --- a/jarvis/cogs/rolegiver.py +++ b/jarvis/cogs/rolegiver.py @@ -49,13 +49,15 @@ class RolegiverCog(Cog): return setting = await Rolegiver.find_one(q(guild=ctx.guild.id)) - if setting and role.id in setting.roles: + if setting and setting.roles and role.id in setting.roles: await ctx.send("Role already in rolegiver", ephemeral=True) return if not setting: setting = Rolegiver(guild=ctx.guild.id, roles=[]) + setting.roles = setting.roles or [] + if len(setting.roles) >= 20: await ctx.send("You can only have 20 roles in the rolegiver", ephemeral=True) return From 6a040a18d4fd5c0cf1ce84ad6ffe48b206525726 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Wed, 4 May 2022 23:56:33 -0600 Subject: [PATCH 360/365] Remove reason from role changes --- jarvis/client.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/jarvis/client.py b/jarvis/client.py index 1e031fd..65065a8 100644 --- a/jarvis/client.py +++ b/jarvis/client.py @@ -30,7 +30,7 @@ from naff.api.events.discord import ( ) from naff.api.events.internal import Button from naff.client.errors import CommandCheckFailure, CommandOnCooldown, HTTPException -from naff.client.utils.misc_utils import find, find_all, get +from naff.client.utils.misc_utils import find_all, get from naff.models.discord.channel import DMChannel from naff.models.discord.embed import Embed, EmbedField from naff.models.discord.enums import AuditLogEventType, Permissions @@ -328,31 +328,18 @@ class Jarvis(Client): if before.roles == after.roles: return - auditlog = await after.guild.fetch_audit_log( - user_id=before.id, action_type=AuditLogEventType.MEMBER_ROLE_UPDATE - ) - new_roles = {} - removed_roles = {} + new_roles = [] + removed_roles = [] for role in before.roles: if role not in after.roles: - reason = "N/A" - for entry in auditlog.entries: - if find(lambda x: x.new_value == role.id, entry.changes): - reason = entry.reason - break - removed_roles[role] = reason + removed_roles.append(role) for role in after.roles: if role not in before.roles: - reason = "N/A" - for entry in auditlog.entries: - if find(lambda x: x.new_value == role.id, entry.changes): - reason = entry.reason - break - new_roles[role] = reason + new_roles.append(role) - new_text = "\n".join(f"{k.mention} [{v}]" for k, v in new_roles.items()) or "None" - removed_text = "\n".join(f"{k.mention} [{v}]" for k, v in removed_roles.items()) or "None" + new_text = "\n".join(role.mention for role in new_roles) or "None" + removed_text = "\n".join(role.mention for role in removed_roles) or "None" fields = ( EmbedField(name="Added Roles", value=new_text), From 828ea921880e6fadeacab6acc5fc409f0267d514 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Thu, 5 May 2022 00:14:29 -0600 Subject: [PATCH 361/365] Add reddit post, support crossposting --- jarvis/cogs/reddit.py | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 1da3474..2f6a900 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -13,7 +13,7 @@ from naff import Client, Cog, InteractionContext, Permissions from naff.client.utils.misc_utils import get from naff.models.discord.channel import ChannelTypes, GuildText from naff.models.discord.components import ActionRow, Select, SelectOption -from naff.models.discord.embed import Embed +from naff.models.discord.embed import Embed, EmbedField from naff.models.naff.application_commands import ( OptionTypes, SlashCommand, @@ -51,7 +51,15 @@ class RedditCog(Cog): await post.author.load() author_url = f"https://reddit.com/u/{post.author.name}" images = [] - content = f"**{post.title}**" + title = f"{post.title}" + fields = [] + content = "" + og_post = None + if not post.is_self: + og_post = post # noqa: F841 + post = await self.api.submission(post.crosspost_parents_list[0]["id"]) + fields.append(EmbedField(name="Crossposted From", value=post.subreddit_name_prefixed)) + content = f"> **{post.title}**\n\n" if "url" in vars(post): if any(post.url.endswith(x) for x in ["jpeg", "jpg", "png", "gif"]): images = [post.url] @@ -66,9 +74,9 @@ class RedditCog(Cog): break if "selftext" in vars(post) and post.selftext: - content += "\n\n" + post.selftext - if len(content) > 600: - content = content[:600] + "..." + content += post.selftext + if len(content) > 900: + content = content[:900] + "..." content += f"\n\n[View this post]({url})" if not images and not content: @@ -79,7 +87,7 @@ class RedditCog(Cog): if "primary_color" in vars(sub): color = sub.primary_color base_embed = build_embed( - title="", + title=title, description=content, fields=[], timestamp=post.created_utc, @@ -384,6 +392,30 @@ class RedditCog(Cog): else: await ctx.send(embeds=embeds) + @reddit.subcommand(sub_cmd_name="post", sub_cmd_description="Get a specific submission") + @slash_option( + name="sid", description="Submission ID", opt_type=OptionTypes.STRING, required=True + ) + async def _reddit_post(self, ctx: InteractionContext, sid: str) -> None: + await ctx.defer() + try: + post = await self.api.submission(sid)() + await post.load() + except (NotFound, Forbidden, Redirect) as e: + self.logger.debug(f"Submission {sid} raised {e.__class__.__name__} in post") + await ctx.send("Subreddit may be private, quarantined, or nonexistent.", ephemeral=True) + return + + embeds = await self.post_embeds(post.subreddit, post) + if post.over_18 and not ctx.channel.nsfw: + try: + await ctx.author.send(embeds=embeds) + await ctx.send("Hey! Due to content, I had to DM the result to you") + except Exception: + await ctx.send("Hey! Due to content, I cannot share the result") + else: + await ctx.send(embeds=embeds) + def setup(bot: Client) -> None: """Add RedditCog to JARVIS""" From 4599bd31bd8c013b74479621299a514d936eef73 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 5 May 2022 00:24:47 -0600 Subject: [PATCH 362/365] Add crosspost support to reddit commands --- jarvis/cogs/reddit.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jarvis/cogs/reddit.py b/jarvis/cogs/reddit.py index 2f6a900..f292f78 100644 --- a/jarvis/cogs/reddit.py +++ b/jarvis/cogs/reddit.py @@ -50,6 +50,7 @@ class RedditCog(Cog): url = "https://reddit.com" + post.permalink await post.author.load() author_url = f"https://reddit.com/u/{post.author.name}" + author_icon = post.author.icon_img images = [] title = f"{post.title}" fields = [] @@ -57,9 +58,10 @@ class RedditCog(Cog): og_post = None if not post.is_self: og_post = post # noqa: F841 - post = await self.api.submission(post.crosspost_parents_list[0]["id"]) + post = await self.api.submission(post.crosspost_parent_list[0]["id"]) + await post.load() fields.append(EmbedField(name="Crossposted From", value=post.subreddit_name_prefixed)) - content = f"> **{post.title}**\n\n" + content = f"> **{post.title}**" if "url" in vars(post): if any(post.url.endswith(x) for x in ["jpeg", "jpg", "png", "gif"]): images = [post.url] @@ -74,7 +76,7 @@ class RedditCog(Cog): break if "selftext" in vars(post) and post.selftext: - content += post.selftext + content += "\n\n" + post.selftext if len(content) > 900: content = content[:900] + "..." content += f"\n\n[View this post]({url})" @@ -89,13 +91,13 @@ class RedditCog(Cog): base_embed = build_embed( title=title, description=content, - fields=[], + fields=fields, timestamp=post.created_utc, url=url, color=color, ) base_embed.set_author( - name="u/" + post.author.name, url=author_url, icon_url=post.author.icon_img + name="u/" + post.author.name, url=author_url, icon_url=author_icon ) base_embed.set_footer( text="Reddit", icon_url="https://www.redditinc.com/assets/images/site/reddit-logo.png" @@ -399,7 +401,7 @@ class RedditCog(Cog): async def _reddit_post(self, ctx: InteractionContext, sid: str) -> None: await ctx.defer() try: - post = await self.api.submission(sid)() + post = await self.api.submission(sid) await post.load() except (NotFound, Forbidden, Redirect) as e: self.logger.debug(f"Submission {sid} raised {e.__class__.__name__} in post") From 04561838e14e0671cff43eb5b330755e29f3650b Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 5 May 2022 10:23:08 -0600 Subject: [PATCH 363/365] Catch keyerror, reported to NAFF --- jarvis/cogs/settings.py | 5 ++++- jarvis/utils/updates.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 3e9c98c..064d37e 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -219,7 +219,10 @@ class SettingsCog(Cog): async for setting in settings: value = setting.value if setting.setting in ["unverified", "verified", "mute"]: - value = await ctx.guild.fetch_role(value) + try: + value = await ctx.guild.fetch_role(value) + except KeyError: + value = None if value: value = value.mention else: diff --git a/jarvis/utils/updates.py b/jarvis/utils/updates.py index c7ee5a8..3dd52f5 100644 --- a/jarvis/utils/updates.py +++ b/jarvis/utils/updates.py @@ -151,7 +151,7 @@ async def update(bot: "Client") -> Optional[UpdateResult]: for module in current_commands.keys(): if module not in new_commands: logger.debug("Module %s removed after update", module) - bot.shed_cog(module) + bot.drop_cog(module) unloaded.append(module) logger.debug("Checking for new/modified commands") From eb2d50fad359a27425738f115350b12218bef5cc Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 5 May 2022 10:25:25 -0600 Subject: [PATCH 364/365] Delete setting if keyerror occurs --- jarvis/cogs/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 064d37e..0559ae2 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -222,6 +222,7 @@ class SettingsCog(Cog): try: value = await ctx.guild.fetch_role(value) except KeyError: + await setting.delete() value = None if value: value = value.mention From 3f938694781d1e7f9f8016b82d8c36abf531bd35 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Thu, 5 May 2022 10:26:31 -0600 Subject: [PATCH 365/365] Delete invalid settings without reporting --- jarvis/cogs/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jarvis/cogs/settings.py b/jarvis/cogs/settings.py index 0559ae2..db2672b 100644 --- a/jarvis/cogs/settings.py +++ b/jarvis/cogs/settings.py @@ -223,7 +223,7 @@ class SettingsCog(Cog): value = await ctx.guild.fetch_role(value) except KeyError: await setting.delete() - value = None + continue if value: value = value.mention else:

eDliiRTGE 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[hWErDVutk(e+9*`l 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 z zM15=bwu$yD8kG6507cZ zK}M9$`NZwY;SfhUi7_K7gs|CwNO%#^*J2a6!MsGdgP~v^tYQak#q)_1)VPQ|T0LFu zx-zF4Olyq7+cU>!1S|j~&*B@A{vcyFm>uz7PTJ0-jrdKvqi2=!JF~3Cj`?;L&a-CD zc$