From 295677fbedcadf9bf79a09fc4aa2ee11d804c205 Mon Sep 17 00:00:00 2001 From: Zevaryx Date: Tue, 19 Apr 2022 17:57:03 -0600 Subject: [PATCH 001/165] 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 002/165] 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 003/165] 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 004/165] 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 005/165] 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 006/165] 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 007/165] 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 008/165] 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 009/165] 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 010/165] 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 011/165] 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 012/165] 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 013/165] 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 014/165] 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 015/165] 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 016/165] 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 017/165] 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 018/165] 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 019/165] 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 020/165] 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 021/165] 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 022/165] 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 023/165] 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 024/165] 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 025/165] 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 026/165] 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 027/165] 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 028/165] 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 029/165] 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 030/165] 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 031/165] 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 032/165] 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 033/165] 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 034/165] 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 035/165] 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 036/165] 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 037/165] 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 038/165] 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 039/165] 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 040/165] 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 041/165] 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 042/165] 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 043/165] 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 044/165] 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 045/165] 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 046/165] 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 047/165] 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 048/165] 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 049/165] 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 050/165] 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 051/165] 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 052/165] 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 053/165] 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 054/165] 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 055/165] 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 056/165] 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 057/165] 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 058/165] 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 059/165] 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 060/165] 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 061/165] 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 062/165] 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 063/165] 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 064/165] 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 065/165] 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 066/165] 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 067/165] 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 068/165] 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 069/165] 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 070/165] 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 071/165] 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 072/165] 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 073/165] 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 074/165] 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 075/165] 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 076/165] 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 077/165] 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 078/165] 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 079/165] 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 080/165] 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 081/165] 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 082/165] 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 083/165] 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 084/165] 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 085/165] 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 086/165] 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 087/165] 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 088/165] 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 089/165] 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 090/165] 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 091/165] 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 092/165] 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 093/165] 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 094/165] 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 095/165] 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 096/165] 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 097/165] 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 098/165] 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 099/165] 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 100/165] 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 101/165] 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 102/165] 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 103/165] 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 104/165] 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 105/165] 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 106/165] 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 107/165] 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 108/165] 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 109/165] 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 110/165] 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 111/165] 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 112/165] 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 113/165] 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 114/165] 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 115/165] 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 116/165] 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 117/165] 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 118/165] 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 119/165] 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 120/165] 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 121/165] 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 122/165] 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 123/165] 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 124/165] 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 125/165] 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 126/165] 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 127/165] 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 128/165] 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 129/165] 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 130/165] 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 131/165] 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 132/165] 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 133/165] 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 134/165] 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 135/165] 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 136/165] 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 137/165] 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 138/165] 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 139/165] 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 140/165] 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 141/165] 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 142/165] 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 143/165] 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 144/165] 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 145/165] 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 146/165] 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 147/165] 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 148/165] 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 149/165] 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 150/165] 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 151/165] 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 152/165] 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 153/165] 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 154/165] 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 155/165] 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 156/165] 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 157/165] 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 158/165] 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 159/165] 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 160/165] 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 161/165] 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 162/165] 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 163/165] 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 164/165] 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 165/165] 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: