From 9f77ac6f955a970c51729d7d2bb1ec7650203315 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:55:34 +0000 Subject: [PATCH 01/42] Something terrible is going down - Rewrite almost the whole bot, _again_, to support multiple chat services - Add Discord as a service - Changes to the command parser to add enum support - Changes to command organization - Lots of other small things that I can't include here this was a pain in the ass, but boy it was worth it! we're one step closer to a universal chatbot framework with this --- poetry.lock | 436 +++++++++++++++---- pyproject.toml | 4 + vyxalbot2/__init__.py | 62 ++- vyxalbot2/chat/__init__.py | 502 ---------------------- vyxalbot2/commands/__init__.py | 61 +++ vyxalbot2/commands/common.py | 143 ++++++ vyxalbot2/commands/discord.py | 7 + vyxalbot2/commands/se.py | 291 +++++++++++++ vyxalbot2/github/__init__.py | 23 +- vyxalbot2/{chat => }/reactions.py | 60 ++- vyxalbot2/services/__init__.py | 32 ++ vyxalbot2/services/discord.py | 110 +++++ vyxalbot2/services/se/__init__.py | 157 +++++++ vyxalbot2/{chat => services/se}/parser.py | 31 +- vyxalbot2/types.py | 30 +- vyxalbot2/userdb.py | 46 +- 16 files changed, 1312 insertions(+), 683 deletions(-) delete mode 100644 vyxalbot2/chat/__init__.py create mode 100644 vyxalbot2/commands/__init__.py create mode 100644 vyxalbot2/commands/common.py create mode 100644 vyxalbot2/commands/discord.py create mode 100644 vyxalbot2/commands/se.py rename vyxalbot2/{chat => }/reactions.py (53%) create mode 100644 vyxalbot2/services/__init__.py create mode 100644 vyxalbot2/services/discord.py create mode 100644 vyxalbot2/services/se/__init__.py rename vyxalbot2/{chat => services/se}/parser.py (87%) diff --git a/poetry.lock b/poetry.lock index e3aea07..982ea06 100644 --- a/poetry.lock +++ b/poetry.lock @@ -222,6 +222,17 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "blinker" +version = "1.7.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"}, + {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"}, +] + [[package]] name = "bs4" version = "0.0.1" @@ -312,101 +323,101 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -479,6 +490,45 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "discord-py" +version = "2.3.2" +description = "A Python wrapper for the Discord API" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "discord.py-2.3.2-py3-none-any.whl", hash = "sha256:9da4679fc3cb10c64b388284700dc998663e0e57328283bbfcfc2525ec5960a6"}, + {file = "discord.py-2.3.2.tar.gz", hash = "sha256:4560f70f2eddba7e83370ecebd237ac09fbb4980dc66507482b0c0e5b8f76b9c"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4,<4" + +[package.extras] +docs = ["sphinx (==4.4.0)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport", "typing-extensions (>=4.3,<5)"] +speed = ["Brotli", "aiodns (>=1.1)", "cchardet (==2.1.7)", "orjson (>=3.5.4)"] +test = ["coverage[toml]", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "typing-extensions (>=4.3,<5)"] +voice = ["PyNaCl (>=1.3.0,<1.6)"] + +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + [[package]] name = "frozenlist" version = "1.4.0" @@ -583,6 +633,29 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "motor" +version = "3.1.2" +description = "Non-blocking MongoDB driver for Tornado or asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "motor-3.1.2-py3-none-any.whl", hash = "sha256:4bfc65230853ad61af447088527c1197f91c20ee957cfaea3144226907335716"}, + {file = "motor-3.1.2.tar.gz", hash = "sha256:80c08477c09e70db4f85c99d484f2bafa095772f1d29b3ccb253270f9041da9a"}, +] + +[package.dependencies] +pymongo = ">=4.1,<5" + +[package.extras] +aws = ["pymongo[aws] (>=4.1,<5)"] +encryption = ["pymongo[encryption] (>=4.1,<5)"] +gssapi = ["pymongo[gssapi] (>=4.1,<5)"] +ocsp = ["pymongo[ocsp] (>=4.1,<5)"] +snappy = ["pymongo[snappy] (>=4.1,<5)"] +srv = ["pymongo[srv] (>=4.1,<5)"] +zstd = ["pymongo[zstd] (>=4.1,<5)"] + [[package]] name = "multidict" version = "6.0.4" @@ -677,6 +750,29 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "odmantic" +version = "0.9.2" +description = "ODMantic, an AsyncIO MongoDB Object Document Mapper for Python using type hints " +optional = false +python-versions = ">=3.7" +files = [ + {file = "odmantic-0.9.2-py3-none-any.whl", hash = "sha256:a82f4fe6a6face24cf0cd0738c9bb32564219cb5a5ed6b0a58e92a4c2048e4d8"}, + {file = "odmantic-0.9.2.tar.gz", hash = "sha256:9780087e1bc2afbd0c1f16b2d18137889dbe7e0df12af0762d6b6b17dadd36be"}, +] + +[package.dependencies] +motor = ">=2.1.0,<3.2.0" +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1" +pymongo = ">=3.11.0,<5.0.0" +typing-extensions = {version = ">=4.2.0", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["ipython (>=7.16.1,<7.17.0)"] +doc = ["mkdocs-macros-plugin (>=0.5.0,<0.6.0)", "mkdocs-material (>=8.4.0,<8.5.0)", "mkdocstrings[python] (>=0.19.0,<0.20.0)", "pydocstyle[toml] (>=6.1.1,<6.2.0)"] +fastapi = ["fastapi (>=0.61.1)"] +test = ["async-asgi-testclient (>=1.4.4,<1.5.0)", "asyncmock (>=0.4.2,<0.5.0)", "black (>=22.3.0,<22.4.0)", "coverage[toml] (>=6.2,<7.0)", "darglint (>=1.8.1,<1.9.0)", "fastapi (>=0.61.1,<0.69.0)", "isort (>=5.8.0,<5.9.0)", "mypy (>=0.961,<1.0)", "pytest (>=7.0,<8.0)", "pytest-asyncio (>=0.16.0,<0.17.0)", "pytest-sugar (>=0.9.5,<0.10.0)", "pytest-xdist (>=2.1.0,<2.2.0)", "pytz (>=2022.1,<2023.0)", "requests (>=2.24.0,<2.25.0)", "ruff (>=0.0.137,<0.1.0)", "semver (>=2.13.0,<2.14.0)", "typer (>=0.4.1,<0.5.0)", "types-pytz (>=2022.1.1,<2022.2.0)", "uvicorn (>=0.17.0,<0.18.0)"] + [[package]] name = "packaging" version = "23.2" @@ -725,6 +821,58 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pydantic" +version = "1.10.13" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, + {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, + {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, + {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, + {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, + {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, + {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, + {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, + {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyjwt" version = "2.8.0" @@ -745,6 +893,108 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pymongo" +version = "4.6.0" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c011bd5ad03cc096f99ffcfdd18a1817354132c1331bed7a837a25226659845f"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:5e63146dbdb1eac207464f6e0cfcdb640c9c5ff0f57b754fa96fe252314a1dc6"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:2972dd1f1285866aba027eff2f4a2bbf8aa98563c2ced14cb34ee5602b36afdf"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:a0be99b599da95b7a90a918dd927b20c434bea5e1c9b3efc6a3c6cd67c23f813"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:9b0f98481ad5dc4cb430a60bbb8869f05505283b9ae1c62bdb65eb5e020ee8e3"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:256c503a75bd71cf7fb9ebf889e7e222d49c6036a48aad5a619f98a0adf0e0d7"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:b4ad70d7cac4ca0c7b31444a0148bd3af01a2662fa12b1ad6f57cd4a04e21766"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5717a308a703dda2886a5796a07489c698b442f5e409cf7dc2ac93de8d61d764"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f7f9feecae53fa18d6a3ea7c75f9e9a1d4d20e5c3f9ce3fba83f07bcc4eee2"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128b1485753106c54af481789cdfea12b90a228afca0b11fb3828309a907e10e"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3077a31633beef77d057c6523f5de7271ddef7bde5e019285b00c0cc9cac1e3"}, + {file = "pymongo-4.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf02c32afa6b67e5861a27183dd98ed88419a94a2ab843cc145fb0bafcc5b28"}, + {file = "pymongo-4.6.0-cp310-cp310-win32.whl", hash = "sha256:b14dd73f595199f4275bed4fb509277470d9b9059310537e3b3daba12b30c157"}, + {file = "pymongo-4.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8adf014f2779992eba3b513e060d06f075f0ab2fb3ad956f413a102312f65cdf"}, + {file = "pymongo-4.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba51129fcc510824b6ca6e2ce1c27e3e4d048b6e35d3ae6f7e517bed1b8b25ce"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2973f113e079fb98515722cd728e1820282721ec9fd52830e4b73cabdbf1eb28"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af425f323fce1b07755edd783581e7283557296946212f5b1a934441718e7528"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec71ac633b126c0775ed4604ca8f56c3540f5c21a1220639f299e7a544b55f9"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec6c20385c5a58e16b1ea60c5e4993ea060540671d7d12664f385f2fb32fe79"}, + {file = "pymongo-4.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85f2cdc400ee87f5952ebf2a117488f2525a3fb2e23863a8efe3e4ee9e54e4d1"}, + {file = "pymongo-4.6.0-cp311-cp311-win32.whl", hash = "sha256:7fc2bb8a74dcfcdd32f89528e38dcbf70a3a6594963d60dc9595e3b35b66e414"}, + {file = "pymongo-4.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6695d7136a435c1305b261a9ddb9b3ecec9863e05aab3935b96038145fd3a977"}, + {file = "pymongo-4.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d603edea1ff7408638b2504905c032193b7dcee7af269802dbb35bc8c3310ed5"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79f41576b3022c2fe9780ae3e44202b2438128a25284a8ddfa038f0785d87019"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f2af6cf82509b15093ce3569229e0d53c90ad8ae2eef940652d4cf1f81e045"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecd9e1fa97aa11bf67472220285775fa15e896da108f425e55d23d7540a712ce"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d2be5c9c3488fa8a70f83ed925940f488eac2837a996708d98a0e54a861f212"}, + {file = "pymongo-4.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab6bcc8e424e07c1d4ba6df96f7fb963bcb48f590b9456de9ebd03b88084fe8"}, + {file = "pymongo-4.6.0-cp312-cp312-win32.whl", hash = "sha256:47aa128be2e66abd9d1a9b0437c62499d812d291f17b55185cb4aa33a5f710a4"}, + {file = "pymongo-4.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:014e7049dd019a6663747ca7dae328943e14f7261f7c1381045dfc26a04fa330"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:288c21ab9531b037f7efa4e467b33176bc73a0c27223c141b822ab4a0e66ff2a"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:747c84f4e690fbe6999c90ac97246c95d31460d890510e4a3fa61b7d2b87aa34"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:055f5c266e2767a88bb585d01137d9c7f778b0195d3dbf4a487ef0638be9b651"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:82e620842e12e8cb4050d2643a81c8149361cd82c0a920fa5a15dc4ca8a4000f"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:6b18276f14b4b6d92e707ab6db19b938e112bd2f1dc3f9f1a628df58e4fd3f0d"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:680fa0fc719e1a3dcb81130858368f51d83667d431924d0bcf249644bce8f303"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3919708594b86d0f5cdc713eb6fccd3f9b9532af09ea7a5d843c933825ef56c4"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db082f728160369d9a6ed2e722438291558fc15ce06d0a7d696a8dad735c236b"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e4ed21029d80c4f62605ab16398fe1ce093fff4b5f22d114055e7d9fbc4adb0"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bea9138b0fc6e2218147e9c6ce1ff76ff8e29dc00bb1b64842bd1ca107aee9f"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0269811661ba93c472c8a60ea82640e838c2eb148d252720a09b5123f2c2fe"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d6a1b1361f118e7fefa17ae3114e77f10ee1b228b20d50c47c9f351346180c8"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e3b0127b260d4abae7b62203c4c7ef0874c901b55155692353db19de4b18bc4"}, + {file = "pymongo-4.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a49aca4d961823b2846b739380c847e8964ff7ae0f0a683992b9d926054f0d6d"}, + {file = "pymongo-4.6.0-cp37-cp37m-win32.whl", hash = "sha256:09c7de516b08c57647176b9fc21d929d628e35bcebc7422220c89ae40b62126a"}, + {file = "pymongo-4.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:81dd1308bd5630d2bb5980f00aa163b986b133f1e9ed66c66ce2a5bc3572e891"}, + {file = "pymongo-4.6.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2f8c04277d879146eacda920476e93d520eff8bec6c022ac108cfa6280d84348"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5802acc012bbb4bce4dff92973dff76482f30ef35dd4cb8ab5b0e06aa8f08c80"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ccd785fafa1c931deff6a7116e9a0d402d59fabe51644b0d0c268295ff847b25"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fe03bf25fae4b95d8afe40004a321df644400fdcba4c8e5e1a19c1085b740888"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:2ca0ba501898b2ec31e6c3acf90c31910944f01d454ad8e489213a156ccf1bda"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:10a379fb60f1b2406ae57b8899bacfe20567918c8e9d2d545e1b93628fcf2050"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a4dc1319d0c162919ee7f4ee6face076becae2abbd351cc14f1fe70af5fb20d9"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ddef295aaf80cefb0c1606f1995899efcb17edc6b327eb6589e234e614b87756"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:518c90bdd6e842c446d01a766b9136fec5ec6cc94f3b8c3f8b4a332786ee6b64"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b80a4ee19b3442c57c38afa978adca546521a8822d663310b63ae2a7d7b13f3a"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb438a8bf6b695bf50d57e6a059ff09652a07968b2041178b3744ea785fcef9b"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3db7d833a7c38c317dc95b54e27f1d27012e031b45a7c24e360b53197d5f6e7"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3729b8db02063da50eeb3db88a27670d85953afb9a7f14c213ac9e3dca93034b"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39a1cd5d383b37285641d5a7a86be85274466ae336a61b51117155936529f9b3"}, + {file = "pymongo-4.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7b0e6361754ac596cd16bfc6ed49f69ffcd9b60b7bc4bcd3ea65c6a83475e4ff"}, + {file = "pymongo-4.6.0-cp38-cp38-win32.whl", hash = "sha256:806e094e9e85d8badc978af8c95b69c556077f11844655cb8cd2d1758769e521"}, + {file = "pymongo-4.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1394c4737b325166a65ae7c145af1ebdb9fb153ebedd37cf91d676313e4a67b8"}, + {file = "pymongo-4.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8273e1abbcff1d7d29cbbb1ea7e57d38be72f1af3c597c854168508b91516c2"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e16ade71c93f6814d095d25cd6d28a90d63511ea396bd96e9ffcb886b278baaa"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:325701ae7b56daa5b0692305b7cb505ca50f80a1288abb32ff420a8a209b01ca"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:cc94f9fea17a5af8cf1a343597711a26b0117c0b812550d99934acb89d526ed2"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:21812453354b151200034750cd30b0140e82ec2a01fd4357390f67714a1bfbde"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:0634994b026336195778e5693583c060418d4ab453eff21530422690a97e1ee8"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ad4f66fbb893b55f96f03020e67dcab49ffde0177c6565ccf9dec4fdf974eb61"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2703a9f8f5767986b4f51c259ff452cc837c5a83c8ed5f5361f6e49933743b2f"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bafea6061d63059d8bc2ffc545e2f049221c8a4457d236c5cd6a66678673eab"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28ae33dc5a0b9cee06e95fd420e42155d83271ab75964baf747ce959cac5f52"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16a534da0e39785687b7295e2fcf9a339f4a20689024983d11afaa4657f8507"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef67fedd863ffffd4adfd46d9d992b0f929c7f61a8307366d664d93517f2c78e"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05c30fd35cc97f14f354916b45feea535d59060ef867446b5c3c7f9b609dd5dc"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c63e3a2e8fb815c4b1f738c284a4579897e37c3cfd95fdb199229a1ccfb638a"}, + {file = "pymongo-4.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e5e193f89f4f8c1fe273f9a6e6df915092c9f2af6db2d1afb8bd53855025c11f"}, + {file = "pymongo-4.6.0-cp39-cp39-win32.whl", hash = "sha256:a09bfb51953930e7e838972ddf646c5d5f984992a66d79da6ba7f6a8d8a890cd"}, + {file = "pymongo-4.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:107a234dc55affc5802acb3b6d83cbb8c87355b38a9457fcd8806bdeb8bce161"}, + {file = "pymongo-4.6.0.tar.gz", hash = "sha256:fb1c56d891f9e34303c451998ef62ba52659648bb0d75b03c5e4ac223a3342c2"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +test = ["pytest (>=7)"] +zstd = ["zstandard"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -828,7 +1078,7 @@ websockets = "^10.4" type = "git" url = "https://github.com/GingerIndustries/sechat.git" reference = "v2" -resolved_reference = "256a27b3b0ccb71aeba7ca54bed8cd1dedd12ac0" +resolved_reference = "d9e6fe4efa23621cd88403ba83a81218213a7747" [[package]] name = "six" @@ -1079,4 +1329,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "dd961e3cff13608ae293e130b560c7b971f79e8af34856e235c1946b599e4d39" +content-hash = "f4051654b3c3b2d9aec30e6f38032ef904ceeb3093524f0bae351233a607798a" diff --git a/pyproject.toml b/pyproject.toml index 97e579e..c09308a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,10 @@ python-dateutil = "^2.8.2" websockets = "^10.4" uwuivy = { git = "https://github.com/Vyxal/uwuivy.git", branch = "v0.1.0" } pyyaml = "^6.0" +odmantic = "^0.9.2" +blinker = "^1.7.0" +pydantic = "<2.0" +discord-py = "^2.3.2" [tool.poetry.group.dev.dependencies] black = {version = "^23.9.1", allow-prereleases = true} diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index a641fdc..145877a 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -33,11 +33,16 @@ from cachetools import LRUCache from dateutil.parser import parse as parseDatetime from uwuivy import uwuipy -from vyxalbot2.github import GitHubApplication +from discord.utils import setup_logging +from motor.motor_asyncio import AsyncIOMotorClient +from vyxalbot2.commands.common import CommonCommands +from vyxalbot2.github import GitHubApplication +from vyxalbot2.reactions import Reactions +from vyxalbot2.services.discord import DiscordService, VBClient +from vyxalbot2.services.se import SEService from vyxalbot2.userdb import UserDB -from vyxalbot2.types import PublicConfigType, PrivateConfigType, MessagesType, AppToken -from vyxalbot2.chat import Chat +from vyxalbot2.types import CommonData, PublicConfigType, PrivateConfigType, MessagesType, AppToken __version__ = "2.0.0" @@ -57,39 +62,36 @@ def __init__( self.privateConfig = privateConfig self.messages = messages self.statuses = list(filter(lambda i: hash(i) != -327901152, statuses)) - self.userDB = UserDB(storagePath, publicConfig["groups"]) with open(privateConfig["pem"], "r") as f: self.privkey = f.read() async def run(self): - self.bot = Bot(logger=self.logger) - await self.bot.authenticate( - self.privateConfig["chat"]["email"], - self.privateConfig["chat"]["password"], - self.privateConfig["chat"]["host"], + userDB = UserDB(AsyncIOMotorClient(self.privateConfig["mongoUrl"]), self.privateConfig["database"]) + + ghApp = GitHubApplication(self.publicConfig, self.privkey, self.privateConfig["appID"], self.privateConfig["account"], self.privateConfig["webhookSecret"]) + + common = CommonData( + self.statuses, + self.messages, + self.publicConfig, + self.privateConfig, + 0, + datetime.now(), + userDB, + ghApp ) - self.session = ClientSession() - self.room = await self.bot.joinRoom(self.privateConfig["chat"]["room"]) - self.ghApp = GitHubApplication(self.room, self.publicConfig, self.privkey, self.privateConfig["appID"], self.privateConfig["account"], self.session, self.privateConfig["webhookSecret"]) - self.chat = Chat(self.room, self.userDB, self.ghApp, self.session, self.publicConfig, self.privateConfig, self.messages, self.statuses) - await self.room.send( - "Well, here we are again." - if random.random() > 0.01 - else "GOOD MORNING, MOTHERF***ERS" - ) - self.startupTime = datetime.now() + reactions = Reactions(self.messages) + self.se = await SEService.create(reactions, common) + self.discord = await DiscordService.create(reactions, common) - self.ghApp.on_shutdown.append(self.shutdown) - return self.ghApp + ghApp.on_shutdown.append(self.shutdown) + return ghApp async def shutdown(self, _): - try: - await self.room.send("Shutting down...") - except RuntimeError: - pass - await wait_for(self.bot.shutdown(), 6) - await wait_for(self.session.close(), 3) + await self.se.shutdown() + await self.discord.shutdown() + def run(): PUBLIC_CONFIG_PATH = os.environ.get("VYXALBOT_CONFIG_PUBLIC", "config.json") @@ -99,11 +101,7 @@ def run(): MESSAGES_PATH = DATA_PATH / "messages.toml" STATUSES_PATH = DATA_PATH / "statuses.txt" - logging.basicConfig( - format="[%(name)s] %(levelname)s: %(message)s", - stream=sys.stdout, - level=logging.INFO, - ) + setup_logging() with open(PUBLIC_CONFIG_PATH, "r") as f: publicConfig = json.load(f) diff --git a/vyxalbot2/chat/__init__.py b/vyxalbot2/chat/__init__.py deleted file mode 100644 index 04096fb..0000000 --- a/vyxalbot2/chat/__init__.py +++ /dev/null @@ -1,502 +0,0 @@ -import inspect -from time import time -from typing import Callable -from datetime import datetime -from string import ascii_letters - -import re -import random -import codecs -import base64 -import json -import subprocess - -from gidgethub import BadRequest, HTTPException as GitHubHTTPException, ValidationError -from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI -from aiohttp import ClientSession -from sechat import EventType -from tinydb.table import Document -from sechat.room import Room -from sechat.events import MessageEvent, EditEvent -from uwuivy import uwuipy - -import yaml -import logging - -from vyxalbot2.chat.reactions import Reactions -from vyxalbot2.github import GitHubApplication -from vyxalbot2.types import EventInfo - -from ..types import AppToken, PrivateConfigType, PublicConfigType, MessagesType -from .parser import CommandParser, ParseError -from ..userdb import UserDB -from ..util import RAPTOR, TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage - -class Chat: - def __init__(self, room: Room, userDB: UserDB, ghClient: GitHubApplication, session: ClientSession, publicConfig: PublicConfigType, privateConfig: PrivateConfigType, messages: MessagesType, statuses: list[str]): - self.room = room - self.userDB = userDB - self.publicConfig = publicConfig - self.privateConfig = privateConfig - self.messages = messages - self.statuses = statuses - self.ghClient = ghClient - self.session = session - - self.logger = logging.getLogger("Chat") - self.editDB: dict[int, tuple[datetime, list[int]]] = {} - self.commands: dict[str, Callable] = {a: b for a, b in self.genCommands()} - self.commandHelp = self.genCommandHelp() - self.parser = CommandParser(self.commands) - self.errorsSinceStartup = 0 - self.startupTime = datetime.now() - - self.room.register(self.onMessage, EventType.MESSAGE) - self.room.register(self.onEdit, EventType.EDIT) - self.reactions = Reactions(room, self, messages) - - def genCommands(self): - for attrName in self.__dir__(): - attr = getattr(self, attrName) - if not (callable(attr) and hasattr(attr, "__name__")): - continue - if not attr.__name__.lower().endswith("command"): - continue - yield re.sub(r"([A-Z])", lambda match: " " + match.group(0).lower(), attr.__name__.removesuffix("Command")), attr - - def genCommandHelp(self): - help: dict[str, list[str]] = {} - for fullName, impl in self.commands.items(): - if impl.__doc__ is None: - continue - name = fullName.split(" ")[0] - signature = inspect.signature(impl) - parameters = [] - for parameter in signature.parameters.values(): - if parameter.name in ("event", "self"): - continue - if parameter.default is not parameter.empty: - parameters.append(f"[{parameter.name}: {parameter.annotation.__name__}]") - else: - parameters.append(f"<{parameter.name}: {parameter.annotation.__name__}>") - message = (f"`!!/{fullName} " + " ".join(parameters)).strip() + "`: " + impl.__doc__ - if name in help: - help[name].append(message) - else: - help[name] = [message] - return help - - async def onMessage(self, room: Room, message: MessageEvent): - if await self.reactions.onMessage(message): - # A reaction ran, so don't get pissy about invalid commands - return - if message.user_id == self.room.userID: - return - if not message.content.startswith("!!/"): - return - sentAt = datetime.now() - response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), EventInfo(message.user_name, message.user_id, message.message_id))] - if not len(response): - return - responseIDs = [await self.room.reply(message.message_id, response[0])] - for line in response[1:]: - responseIDs.append(await self.room.send(line)) - self.editDB[message.message_id] = (sentAt, responseIDs) - - async def onEdit(self, room: Room, edit: EditEvent): - if edit.user_id == self.room.userID: - return - if not edit.content.startswith("!!/"): - return - if edit.message_id not in self.editDB: - await self.onMessage(room, edit) - else: - sentAt, idents = self.editDB[edit.message_id] - if (datetime.now() - sentAt).seconds > (60 * 2): # margin of error - await self.onMessage(room, edit) - return - response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), EventInfo(edit.user_name, edit.user_id, edit.message_id))] - if len(response): - response[0] = f":{edit.message_id} " + response[0] - for x in range(min(len(idents), len(response))): - await self.room.edit(idents.pop(0), response.pop(0)) - for leftover in response: - await self.room.send(leftover) - for leftover in idents: - await self.room.delete(leftover) - self.editDB.pop(edit.message_id) - for key, value in self.editDB.copy().items(): - if (datetime.now() - value[0]).seconds > (60 * 2): - self.editDB.pop(key) - - async def processMessage(self, message: str, event: EventInfo): - try: - commandName, impl, args = self.parser.parseCommand(message) - except ParseError as e: - yield "Command error: " + e.message - return - userInfo = self.userDB.getUserInfo(event.userIdent) - for groupName, group in self.publicConfig["groups"].items(): - if commandName in group.get("canRun", []): - if userInfo is not None: - if groupName not in userInfo["groups"]: - yield f"Only members of group {groupName} can run !!/{commandName}." - return - else: - yield f"Only members of group {groupName} can run !!/{commandName}." - return - try: - async for l in impl(event, *args): - yield l - except Exception as e: - yield f"@Ginger An exception occured whilst processing this message!" - self.logger.exception(f"An exception occured whilst processing message {event.messageIdent}:") - - async def dieCommand(self, event: EventInfo): - exit(-42) - - async def helpCommand(self, event: EventInfo, command: str = ""): - """Provide help for a command.""" - if command: - if command == "me": - yield "I'd love to, but I don't have any limbs." - elif command == "syntax": - yield self.messages["syntaxhelp"] - else: - if command in self.commandHelp: - for line in self.commandHelp[command]: - yield line - else: - yield "No help is available for that command." - else: - yield self.messages["help"] + ", ".join(sorted(set(map(lambda i: i.split(" ")[0], self.commands.keys())))) - - async def infoCommand(self, event: EventInfo): - yield self.messages["info"] - - def status(self): - return ( - f"Bot status: Online\n" - f"Uptime: {datetime.now() - self.startupTime}\n" - f"Running since: {self.startupTime.isoformat()}\n" - f"Errors since startup: {self.errorsSinceStartup}" - ) - - async def statusCommand(self, event: EventInfo): - """I will tell you what I'm doing (maybe).""" - status = random.choice(self.statuses) - if not status.endswith(".") and status.endswith(tuple(ascii_letters)): - status += "." - else: - status = status.removesuffix(";") - yield status - - async def statusBoringCommand(self, event: EventInfo): - """Get actual status information about the bot.""" - yield self.status() - - async def statusExcitingCommand(self, event: EventInfo): - yield "\n".join(map(lambda line: line + ("!" * random.randint(2, 5)), self.status().upper().splitlines())) - - async def statusTinglyCommand(self, event: EventInfo): - uwu = uwuipy(None, 0.3, 0.2, 0.2, 1) # type: ignore Me when the developers of uwuipy don't annotate their types correctly - yield uwu.uwuify(self.status()) - - async def statusSleepyCommand(self, event: EventInfo): - status = self.status() - yield ( - "\n".join(status.splitlines())[:random.randint(1, len(status.splitlines()))] - + " *yawn*\n" - + "z" * random.randint(5, 10) - ) - - async def statusCrypticCommand(self, event: EventInfo): - yield codecs.encode(self.status(), "rot13") - - async def statusGoofyCommand(self, event: EventInfo): - yield "\n".join(map(lambda line: line + "🤓" * random.randint(1, 3), self.status().splitlines())) - - def getPermissionsTarget(self, event: EventInfo, name: str) -> Document | str: - if name == "me": - target = self.userDB.getUserInfo(event.userIdent) - if target is None: - return "You are not in my database. Please run !!/register." - else: - target = self.userDB.getUserInfoByName(name) - if target is None: - return "I don't know any user by that name." - return target - - async def permissionsListCommand(self, event: EventInfo, name: str): - """List the groups a user is member of.""" - if isinstance(target := self.getPermissionsTarget(event, name), str): - yield target - return - yield f"User {target['name']} is a member of groups {', '.join(target['groups'])}." - - def permissionsModify(self, event: EventInfo, name: str, group: str, grant: bool): - if isinstance(target := self.getPermissionsTarget(event, name), str): - yield target - return - sender = self.userDB.getUserInfo(event.userIdent) - if sender is None: - yield "You are not in my database. Please run !!/register." - return - group = group.removesuffix("s") - try: - promotionRequires = self.publicConfig["groups"][group].get("promotionRequires", []) - except KeyError: - yield "That group does not exist." - return - if (not any([i in promotionRequires for i in sender["groups"]])) and len(promotionRequires): - yield "Insufficient permissions." - return - if grant: - if self.userDB.addUserToGroup(target, group): - yield f"Added {target['name']} to {group}." - else: - yield f"{target['name']} is already a member of {group}." - else: - if target["chatID"] in self.publicConfig["groups"][group].get("protected", []): - yield "That user may not be removed." - else: - self.userDB.removeUserFromGroup(target, group) - yield f"{target['name']} removed from {group}." - - async def permissionsGrantCommand(self, event: EventInfo, name: str, group: str): - """Add a user to a group.""" - for line in self.permissionsModify(event, name, group, True): - yield line - async def permissionsRevokeCommand(self, event: EventInfo, name: str, group: str): - """Remove a user from a group.""" - for line in self.permissionsModify(event, name, group, False): - yield line - - async def registerCommand(self, event: EventInfo): - """Register yourself to the bot.""" - if self.userDB.getUserInfo(event.userIdent): - yield "You are already registered. If your details are out of date, run !!/refresh." - return - self.userDB.addUserToDatabase( - await ( - await self.session.get( - f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" - ) - ).json() - ) - yield "You have been registered! You don't have any permisssions yet." - - async def refreshCommand(self, event: EventInfo): - """Refresh your user information.""" - if self.userDB.getUserInfo(event.userIdent) is None: - yield "You are not in my database. Please run !!/register." - return - self.userDB.refreshUserData( - await ( - await self.session.get( - f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" - ) - ).json() - ) - yield "Your details have been updated." - - async def groupsListCommand(self, event: EventInfo): - """List all groups known to the bot.""" - yield "All groups: " + ", ".join(self.publicConfig['groups'].keys()) - async def groupsMembersCommand(self, event: EventInfo, group: str): - """List all members of a group.""" - group = group.removesuffix("s") - yield f"Members of {group}: " + ', '.join(map(lambda i: i['name'], self.userDB.membersOfGroup(group))) - - async def pingCommand(self, event: EventInfo, group: str, message: str): - """Ping all members of a group. Use with care!""" - group = group.removesuffix("s") - pings = " ".join(["@" + target["name"] for target in self.userDB.membersOfGroup(group) if target["chatID"] != event.userIdent]) - if not len(pings): - yield "Nobody to ping." - else: - yield pings + " ^" - - async def coffeeCommand(self, event: EventInfo, target: str = "me"): - """Brew some coffee.""" - if target == "me" or not len(target): - yield "☕" - else: - yield f"@{target} ☕" - - async def maulCommand(self, event: EventInfo, target: str): - """Summon the raptors.""" - if target.lower().removesuffix("2") == "vyxalbot" or target == "me": - yield RAPTOR.format(user=event.userName) - else: - yield RAPTOR.format(user=target) - - async def hugCommand(self, event: EventInfo): - """<3""" - yield random.choice(self.messages["hugs"]) - - async def susCommand(self, event: EventInfo): - """STOP POSTING ABOUT AMONG US""" - yield "āļž" * random.randint(8, 64) - - async def amilyxalCommand(self, event: EventInfo): - yield f"You are {'' if (event.userIdent == 354515) != (random.random() <= 0.1) else 'not '}lyxal." - - async def blameCommand(self, event: EventInfo): - yield f"It was {random.choice(self.userDB.users())['name']}'s fault!" - - async def cookieCommand(self, event: EventInfo): - """Bake a cookie. Maybe. You have to be worthy.""" - if info := self.userDB.getUserInfo(event.userIdent): - if "admin" in info["groups"]: - yield "Here you go: đŸĒ" - else: - if random.random() <= 0.75: - yield "Here you go: đŸĒ" - else: - yield "No." - - async def issueOpenCommand(self, event: EventInfo, repo: str, title: str, body: str, tags: list[str] = []): - """Open an issue in a repository.""" - tagSet = set(tags) - if repo in self.publicConfig["requiredLabels"]: - requiredLabels = self.publicConfig["requiredLabels"][repo] - for rule in requiredLabels["issues"]: - labelSet = set(rule["tags"]) - if rule["exclusive"]: - if len(labelSet.intersection(tagSet)) != 1: - yield f"Must be tagged with exactly one of " + ", ".join(f"`{i}`" for i in labelSet) - return - else: - if len(labelSet.intersection(tagSet)) < 1: - yield f"Must be tagged with one or more of " + ", ".join(f"`{i}`" for i in labelSet) - return - body = body + ( - f"\n\n_Issue created by {event.userName} [here]" - f'(https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})' - "_" - ) - try: - await self.ghClient.gh.post( - f"/repos/{self.privateConfig['account']}/{repo}/issues", - data={ - "title": title, - "body": body, - "labels": tags - }, - oauth_token = await self.ghClient.appToken() - ) - except BadRequest as e: - yield f"Failed to open issue: {e.args}" - - async def prodCommand(self, event: EventInfo, repo: str = ""): - """Open a PR to update production.""" - if len(repo) == 0: - repo = self.privateConfig["baseRepo"] - if repo not in self.publicConfig["production"]: - yield "Repository not configured." - return - try: - await self.ghClient.gh.post( - f"/repos/{self.privateConfig['account']}/{repo}/pulls", - data={ - "title": f"Update production ({datetime.now().strftime('%b %d %Y')})", - "head": self.publicConfig["production"][repo]["head"], - "base": self.publicConfig["production"][repo]["base"], - "body": f"Requested by {event.userName} [here]({f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})'}.", - }, - oauth_token=await self.ghClient.appToken() - ) - except ValidationError as e: - yield f"Unable to open PR: {e}" - except GitHubHTTPException as e: - yield f"Failed to create issue: {e.status_code.value} {e.status_code.description}", - - async def idiomAddCommand(self, event: EventInfo, title: str, code: str, description: str, keywords: list[str] = []): - """Add an idiom to the idiom list.""" - file = await self.ghClient.gh.getitem( - f"/repos/{self.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", - oauth_token=await self.ghClient.appToken(), - ) - idioms = yaml.safe_load(base64.b64decode(file["content"])) - if not idioms: - idioms = [] - idioms.append( - { - "name": title, - "code": code, - "description": description, - "link": "#" - + base64.b64encode( - json.dumps(["", "", "", code, ""]).encode( - "utf-8" - ) - ).decode("utf-8"), - "keywords": keywords, - } - ) - await self.ghClient.gh.put( - f"/repos/{self.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", - data={ - "message": f"Added \"{title}\" to the idiom list.\nRequested by {event.userName} here: {f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent}'}", - "content": base64.b64encode( - yaml.dump( - idioms, encoding="utf-8", allow_unicode=True - ) - ).decode("utf-8"), - "sha": file["sha"], - }, - oauth_token=await self.ghClient.appToken(), - ) - - async def deliterateifyCommand(self, event: EventInfo, code: str): - """Convert literate code to sbcs""" - async with self.session.post(self.privateConfig["tyxalInstance"] + "/deliterateify", data=code) as response: - if response.status == 400: - yield f"Failed to illiterateify: {await response.text()}" - elif response.status == 200: - yield f"`{await response.text()}`" - else: - yield f"Tyxal sent back an error response! ({response.status})" - # Add an alias - async def delitCommand(self, event: EventInfo, code: str): - async for line in self.deliterateifyCommand(event, code): - yield line - - async def trashCommand(self, event: EventInfo, startRaw: str, endRaw: str, target: int = TRASH): - """Move messages to a room (defaults to Trash).""" - start = extractMessageIdent(startRaw) - end = extractMessageIdent(endRaw) - if start is None: - yield "Malformed start id" - return - if end is None: - yield "Malformed end id" - return - # Sanity check: make sure the messages are actually in our room - if (await getRoomOfMessage(self.session, start)) != self.privateConfig["chat"]["room"]: - yield "Start message does not exist or is not in this room" - return - if (await getRoomOfMessage(self.session, start)) != self.privateConfig["chat"]["room"]: - yield "End message does not exist or is not in this room" - return - # Dubious code to figure out the range of messages we're dealing with - identRange = [i async for i in getMessageRange(self.session, self.privateConfig["chat"]["room"], start, end)] - await self.room.moveMessages(identRange, target) - yield f"Moved {len(identRange)} messages successfully." - - async def pullCommand(self, event: EventInfo): - """Pull changes and restart.""" - if subprocess.run(["git", "pull"]).returncode == 0: - yield "Restarting..." - exit(-43) - else: - yield "Failed to pull!" - - async def commitCommand(self, event: EventInfo): - """Check the commit the bot is running off of""" - result = subprocess.run(["git", "show", "--oneline", "-s", "--no-color"], capture_output=True) - if result.returncode != 0: - yield "Failed to get commit info!" - else: - yield f"Commit: {result.stdout.decode('utf-8').strip()}" diff --git a/vyxalbot2/commands/__init__.py b/vyxalbot2/commands/__init__.py new file mode 100644 index 0000000..49a2a46 --- /dev/null +++ b/vyxalbot2/commands/__init__.py @@ -0,0 +1,61 @@ +from collections import UserDict +from dataclasses import dataclass +from enum import Enum +from typing import Any, AsyncGenerator, Callable, Self + +import re +import inspect + + +from vyxalbot2.types import EventInfo + +class Command(dict[str, Self]): + def __init__(self, name: str, doc: str, impl: Callable[..., AsyncGenerator[Any, None]]): + super().__init__() + self.name = name + self.helpStr = doc + self.impl = impl + + def __hash__(self): + return hash(self.name) + + @property + def fullHelp(self): + parameters = [] + for parameter in inspect.signature(self.impl).parameters.values(): + if parameter.name in ("event", "self"): + continue + if issubclass(parameter.annotation, Enum): + typeString = "|".join(member.value for member in parameter.annotation) + if parameter.default is not parameter.empty: + assert isinstance(parameter.default, parameter.annotation) + typeString += " = " + parameter.default.value + else: + typeString = parameter.annotation.__name__ + if parameter.default is not parameter.empty: + parameters.append(f"[{parameter.name}: {typeString}]") + else: + parameters.append(f"<{parameter.name}: {typeString}>") + return (f"`!!/{self.name} " + " ".join(parameters)).strip() + "`: " + self.helpStr + +class CommandSupplier: + def __init__(self): + self.commands = self.genCommands() + + def invoke(self, name: str, event: EventInfo, *args): + return self.commands[name].impl(event, *args) + + def genCommands(self): + commands: dict[str, Command] = {} + for attrName in self.__dir__(): + attr = getattr(self, attrName) + if not (callable(attr) and hasattr(attr, "__name__")): + continue + if not attr.__name__.lower().endswith("command"): + continue + if attr.__doc__ is None: + continue + name = re.sub(r"([A-Z])", lambda match: " " + match.group(0).lower(), attr.__name__.removesuffix("Command")) + commands[name] = Command(name, attr.__doc__, attr) + + return commands \ No newline at end of file diff --git a/vyxalbot2/commands/common.py b/vyxalbot2/commands/common.py new file mode 100644 index 0000000..c48b486 --- /dev/null +++ b/vyxalbot2/commands/common.py @@ -0,0 +1,143 @@ +from datetime import datetime +from enum import Enum +from imaplib import Commands +from string import ascii_letters + +import codecs +import random +import subprocess + +from aiohttp import ClientSession +from discord import Status + +from vyxalbot2.commands import CommandSupplier +from vyxalbot2.types import CommonData, EventInfo +from vyxalbot2.util import RAPTOR +from uwuivy import uwuipy + +class StatusMood(Enum): + MESSAGE = "message" + BORING = "boring" + EXCITING = "exciting" + TINGLY = "tingly" + SLEEPY = "sleepy" + CRYPTIC = "cryptic" + GOOFY = "goofy" + +class CommonCommands(CommandSupplier): + def __init__(self, common: CommonData): + super().__init__() + self.common = common + + async def dieCommand(self, event: EventInfo): + exit(-42) + + async def infoCommand(self, event: EventInfo): + yield self.common.messages["info"] + + def status(self): + return ( + f"Bot status: Online\n" + f"Uptime: {datetime.now() - self.common.startupTime}\n" + f"Running since: {self.common.startupTime.isoformat()}\n" + f"Errors since startup: {self.common.errorsSinceStartup}" + ) + + async def statusCommand(self, event: EventInfo, mood: StatusMood = StatusMood.MESSAGE): + """I will tell you what I'm doing (maybe).""" + match mood: + case StatusMood.MESSAGE: + status = random.choice(self.common.statuses) + if not status.endswith(".") and status.endswith(tuple(ascii_letters)): + status += "." + else: + status = status.removesuffix(";") + yield status + case StatusMood.BORING: + yield self.status() + case StatusMood.EXCITING: + yield "\n".join(map(lambda line: line + ("!" * random.randint(2, 5)), self.status().upper().splitlines())) + case StatusMood.TINGLY: + uwu = uwuipy(None, 0.3, 0.2, 0.2, 1) # type: ignore Me when the developers of uwuipy don't annotate their types correctly + yield uwu.uwuify(self.status()) + case StatusMood.SLEEPY: + status = self.status() + yield ( + "\n".join(status.splitlines())[:random.randint(1, len(status.splitlines()))] + + " *yawn*\n" + + "z" * random.randint(5, 10) + ) + case StatusMood.CRYPTIC: + yield codecs.encode(self.status(), "rot13") + case StatusMood.GOOFY: + yield "\n".join(map(lambda line: line + "🤓" * random.randint(1, 3), self.status().splitlines())) + + async def coffeeCommand(self, event: EventInfo, target: str = "me"): + """Brew some coffee.""" + if target == "me" or not len(target): + yield "☕" + else: + yield f"@{target} ☕" + + async def maulCommand(self, event: EventInfo, target: str): + """Summon the raptors.""" + if target.lower().removesuffix("2") == "vyxalbot" or target == "me": + yield RAPTOR.format(user=event.userName) + else: + yield RAPTOR.format(user=target) + + async def hugCommand(self, event: EventInfo): + """<3""" + yield random.choice(self.common.messages["hugs"]) + + async def susCommand(self, event: EventInfo): + """STOP POSTING ABOUT AMONG US""" + yield "āļž" * random.randint(8, 64) + + async def amilyxalCommand(self, event: EventInfo): + yield f"You are {'' if (event.userIdent == 354515) != (random.random() <= 0.1) else 'not '}lyxal." + + async def blameCommand(self, event: EventInfo): + yield f"It was {random.choice(await self.common.userDB.getUsers(event.service)).name}'s fault!" + + async def cookieCommand(self, event: EventInfo): + """Bake a cookie. Maybe. You have to be worthy.""" + if info := (await self.common.userDB.getUser(event.service, event.userIdent)): + if "admin" in info.groups: + yield "Here you go: đŸĒ" + else: + if random.random() <= 0.75: + yield "Here you go: đŸĒ" + else: + yield "No." + + async def deliterateifyCommand(self, event: EventInfo, code: str): + """Convert literate code to sbcs""" + async with ClientSession() as session: + async with session.post(self.common.privateConfig["tyxalInstance"] + "/deliterateify", data=code) as response: + if response.status == 400: + yield f"Failed to deliterateify: {await response.text()}" + elif response.status == 200: + yield f"`{await response.text()}`" + else: + yield f"Tyxal sent back an error response! ({response.status})" + # Add an alias + async def delitCommand(self, event: EventInfo, code: str): + async for line in self.deliterateifyCommand(event, code): + yield line + + async def pullCommand(self, event: EventInfo): + """Pull changes and restart.""" + if subprocess.run(["git", "pull"]).returncode == 0: + yield "Restarting..." + exit(-43) + else: + yield "Failed to pull!" + + async def commitCommand(self, event: EventInfo): + """Check the commit the bot is running off of""" + result = subprocess.run(["git", "show", "--oneline", "-s", "--no-color"], capture_output=True) + if result.returncode != 0: + yield "Failed to get commit info!" + else: + yield f"Commit: {result.stdout.decode('utf-8').strip()}" \ No newline at end of file diff --git a/vyxalbot2/commands/discord.py b/vyxalbot2/commands/discord.py new file mode 100644 index 0000000..2180704 --- /dev/null +++ b/vyxalbot2/commands/discord.py @@ -0,0 +1,7 @@ +from vyxalbot2.commands.common import CommonCommands +from vyxalbot2.types import CommonData + + +class DiscordCommands(CommonCommands): + def __init__(self, common: CommonData): + super().__init__(common) \ No newline at end of file diff --git a/vyxalbot2/commands/se.py b/vyxalbot2/commands/se.py new file mode 100644 index 0000000..b4f5bd3 --- /dev/null +++ b/vyxalbot2/commands/se.py @@ -0,0 +1,291 @@ +from datetime import datetime +from typing import Union, TYPE_CHECKING + +import base64 +import json +import yaml + +from aiohttp import ClientSession +from gidgethub import BadRequest, ValidationError, HTTPException as GitHubHTTPException +from sechat import Room +if TYPE_CHECKING: + from vyxalbot2.services.se import SEService +from vyxalbot2.commands.common import CommonCommands +from vyxalbot2.types import CommonData, EventInfo +from vyxalbot2.userdb import User +from urllib.parse import urlparse, urlunparse + +from vyxalbot2.util import TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage + +STACK_IMGUR = "i.stack.imgur.com" +DEFAULT_PFP = "https://cdn-chat.sstatic.net/chat/img/anon.png" + +class SECommands(CommonCommands): + def __init__(self, room: Room, common: CommonData, service: "SEService"): + super().__init__(common) + self.room = room + self.service = service + self.userDB = common.userDB + self.groups = common.publicConfig["groups"] + self.commandHelp = self.genHelpStrings() + + def genHelpStrings(self): + help: dict[str, list[str]] = {} + for name, command in self.commands.items(): + baseName = name.split(" ")[0] + if baseName in help: + help[baseName].append(command.fullHelp) + else: + help[baseName] = [command.fullHelp] + return help + + def resolveChatPFP(self, pfp: str): + if pfp.startswith("!"): + pfp = pfp.removeprefix("!") + url = urlparse(pfp) + if url.netloc == STACK_IMGUR: + return urlunparse(("https", STACK_IMGUR, url.path, "", "s=256", "")) + return pfp + return f"https://www.gravatar.com/avatar/{pfp}?s=256&d=identicon&r=PG" + + async def helpCommand(self, event: EventInfo, command: str = ""): + """Provide help for a command.""" + if command: + if command == "me": + yield "I'd love to, but I don't have any limbs." + elif command == "syntax": + yield self.common.messages["syntaxhelp"] + else: + if command in self.commandHelp: + for line in self.commandHelp[command]: + yield line + else: + yield "No help is available for that command." + else: + yield self.common.messages["help"] + ", ".join(sorted(set(map(lambda i: i.split(" ")[0], event.service.commands.commands.keys())))) + + async def getPermissionsTarget(self, event: EventInfo, name: str) -> Union[User, str]: + if name == "me": + target = await self.userDB.getUser(self.service, event.userIdent) + if target is None: + return "You are not in my database. Please run !!/register." + else: + target = await self.userDB.getUserByName(self.service, name) + if target is None: + return "I don't know any user by that name." + return target + + async def permissionsListCommand(self, event: EventInfo, name: str): + """List the groups a user is member of.""" + if isinstance(target := (await self.getPermissionsTarget(event, name)), str): + yield target + return + yield f"User {target.name} is a member of groups {', '.join(target.groups)}." + + async def permissionsModify(self, event: EventInfo, name: str, group: str, grant: bool): + if isinstance(target := (await self.getPermissionsTarget(event, name)), str): + yield target + return + sender = await self.userDB.getUser(self.service, event.userIdent) + if sender is None: + yield "You are not in my database. Please run !!/register." + return + group = group.removesuffix("s") + try: + promotionRequires = self.groups[group].get("promotionRequires", []) + except KeyError: + yield "That group does not exist." + return + if (not any([i in promotionRequires for i in sender.groups])) and len(promotionRequires): + yield "Insufficient permissions." + return + if grant: + if group in target.groups: + yield f"{target.name} is already a member of {group}." + else: + target.groups.append(group) + else: + if target.serviceIdent in self.groups[group].get("protected", {}).get(self.service.name, []): + yield "That user may not be removed." + elif group not in target.groups: + yield f"That user is not in {group}." + else: + target.groups.remove(group) + yield f"{target.name} removed from {group}." + await self.userDB.save(target) + + async def permissionsGrantCommand(self, event: EventInfo, name: str, group: str): + """Add a user to a group.""" + async for line in self.permissionsModify(event, name, group, True): + yield line + async def permissionsRevokeCommand(self, event: EventInfo, name: str, group: str): + """Remove a user from a group.""" + async for line in self.permissionsModify(event, name, group, False): + yield line + + async def registerCommand(self, event: EventInfo): + """Register yourself to the bot.""" + if await self.userDB.getUser(self.service, event.userIdent): + yield "You are already registered. If your details are out of date, run !!/refresh." + return + async with ClientSession() as session: + async with session.get( + f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" + ) as response: + thumb = await response.json() + await self.userDB.createUser( + self.service, + thumb["id"], + thumb["name"], + self.resolveChatPFP(thumb["email_hash"]) + ) + yield "You have been registered! You don't have any permisssions yet." + + async def refreshCommand(self, event: EventInfo): + """Refresh your user information.""" + user = await self.userDB.getUser(self.service, event.userIdent) + if user is None: + yield "You are not in my database. Please run !!/register." + return + async with ClientSession() as session: + async with session.get( + f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" + ) as response: + thumb = await response.json() + user.name = thumb["name"] + user.pfp = self.resolveChatPFP(thumb["email_hash"]) + await self.userDB.save(user) + yield "Your details have been updated." + + async def groupsListCommand(self, event: EventInfo): + """List all groups known to the bot.""" + yield "All groups: " + ", ".join(self.groups.keys()) + async def groupsMembersCommand(self, event: EventInfo, group: str): + """List all members of a group.""" + group = group.removesuffix("s") + yield f"Members of {group}: " + ', '.join(map(lambda i: i.name, await self.userDB.membersOfGroup(self.service, group))) + + async def pingCommand(self, event: EventInfo, group: str, message: str): + """Ping all members of a group. Use with care!""" + group = group.removesuffix("s") + pings = " ".join(["@" + target.name for target in await self.userDB.membersOfGroup(self.service, group) if target.serviceIdent != event.userIdent]) + if not len(pings): + yield "Nobody to ping." + else: + yield pings + " ^" + + async def issueOpenCommand(self, event: EventInfo, repo: str, title: str, body: str, tags: list[str] = []): + """Open an issue in a repository.""" + tagSet = set(tags) + if repo in self.common.publicConfig["requiredLabels"]: + requiredLabels = self.common.publicConfig["requiredLabels"][repo] + for rule in requiredLabels["issues"]: + labelSet = set(rule["tags"]) + if rule["exclusive"]: + if len(labelSet.intersection(tagSet)) != 1: + yield f"Must be tagged with exactly one of " + ", ".join(f"`{i}`" for i in labelSet) + return + else: + if len(labelSet.intersection(tagSet)) < 1: + yield f"Must be tagged with one or more of " + ", ".join(f"`{i}`" for i in labelSet) + return + body = body + ( + f"\n\n_Issue created by {event.userName} [here]" + f'(https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})' + "_" + ) + try: + await self.common.ghClient.gh.post( + f"/repos/{self.common.privateConfig['account']}/{repo}/issues", + data={ + "title": title, + "body": body, + "labels": tags + }, + oauth_token = await self.common.ghClient.appToken() + ) + except BadRequest as e: + yield f"Failed to open issue: {e.args}" + + async def prodCommand(self, event: EventInfo, repo: str = ""): + """Open a PR to update production.""" + if len(repo) == 0: + repo = self.common.privateConfig["baseRepo"] + if repo not in self.common.publicConfig["production"]: + yield "Repository not configured." + return + try: + await self.common.ghClient.gh.post( + f"/repos/{self.common.privateConfig['account']}/{repo}/pulls", + data={ + "title": f"Update production ({datetime.now().strftime('%b %d %Y')})", + "head": self.common.publicConfig["production"][repo]["head"], + "base": self.common.publicConfig["production"][repo]["base"], + "body": f"Requested by {event.userName} [here]({f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})'}.", + }, + oauth_token=await self.common.ghClient.appToken() + ) + except ValidationError as e: + yield f"Unable to open PR: {e}" + except GitHubHTTPException as e: + yield f"Failed to create issue: {e.status_code.value} {e.status_code.description}", + + async def idiomAddCommand(self, event: EventInfo, title: str, code: str, description: str, keywords: list[str] = []): + """Add an idiom to the idiom list.""" + file = await self.common.ghClient.gh.getitem( + f"/repos/{self.common.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", + oauth_token=await self.common.ghClient.appToken(), + ) + idioms = yaml.safe_load(base64.b64decode(file["content"])) + if not idioms: + idioms = [] + idioms.append( + { + "name": title, + "code": code, + "description": description, + "link": "#" + + base64.b64encode( + json.dumps(["", "", "", code, ""]).encode( + "utf-8" + ) + ).decode("utf-8"), + "keywords": keywords, + } + ) + await self.common.ghClient.gh.put( + f"/repos/{self.common.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", + data={ + "message": f"Added \"{title}\" to the idiom list.\nRequested by {event.userName} here: {f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent}'}", + "content": base64.b64encode( + yaml.dump( + idioms, encoding="utf-8", allow_unicode=True + ) + ).decode("utf-8"), + "sha": file["sha"], + }, + oauth_token=await self.common.ghClient.appToken(), + ) + + async def trashCommand(self, event: EventInfo, startRaw: str, endRaw: str, target: int = TRASH): + """Move messages to a room (defaults to Trash).""" + async with ClientSession() as session: + start = extractMessageIdent(startRaw) + end = extractMessageIdent(endRaw) + if start is None: + yield "Malformed start id" + return + if end is None: + yield "Malformed end id" + return + # Sanity check: make sure the messages are actually in our room + if (await getRoomOfMessage(session, start)) != self.common.privateConfig["chat"]["room"]: + yield "Start message does not exist or is not in this room" + return + if (await getRoomOfMessage(session, start)) != self.common.privateConfig["chat"]["room"]: + yield "End message does not exist or is not in this room" + return + # Dubious code to figure out the range of messages we're dealing with + identRange = [i async for i in getMessageRange(session, self.common.privateConfig["chat"]["room"], start, end)] + await self.room.moveMessages(identRange, target) + yield f"Moved {len(identRange)} messages successfully." \ No newline at end of file diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index b22f7d8..0fbcd27 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -12,6 +12,7 @@ from cachetools import LRUCache import jwt from sechat import Room +from vyxalbot2.services import PinThat, Service from vyxalbot2.types import AppToken, PublicConfigType @@ -19,15 +20,15 @@ from vyxalbot2.util import GITHUB_MERGE_QUEUE def wrap(fun): - async def wrapper(self: "GitHubApplication", event: GitHubEvent, gh: AsyncioGitHubAPI): + async def wrapper(self: "GitHubApplication", service: Service, event: GitHubEvent, gh: AsyncioGitHubAPI): async for line in fun(self, event): - await self.room.send(line) + await service.send(line) return wrapper class GitHubApplication(Application): - def __init__(self, room: Room, publicConfig: PublicConfigType, privkey: str, appId: str, account: str, session: ClientSession, webhookSecret: str): + def __init__(self, publicConfig: PublicConfigType, privkey: str, appId: str, account: str, webhookSecret: str): super().__init__() - self.room = room + self.services = [] self.privkey = privkey self.appId = appId self.account = account @@ -37,7 +38,7 @@ def __init__(self, room: Room, publicConfig: PublicConfigType, privkey: str, app self._appToken: Optional[AppToken] = None self.ghRouter = Router() self.cache = LRUCache(maxsize=5000) - self.gh = AsyncioGitHubAPI(session, "VyxalBot2", cache=self.cache) + self.gh = AsyncioGitHubAPI(ClientSession(), "VyxalBot2", cache=self.cache) self.router.add_post("/webhook", self.onHookRequest) self.ghRouter.add(self.onPushAction, "push") @@ -109,7 +110,8 @@ async def onHookRequest(self, request: Request) -> Response: msg = f"An error occured while processing a request!" self.logger.exception(msg) try: - await self.room.send(f"@Ginger " + msg) + for service in self.services: + await service.send(f"@Ginger " + msg) except RuntimeError: pass return Response(status=500) @@ -232,16 +234,13 @@ async def onReleaseCreated(self, event: GitHubEvent): # attempt to match version number, otherwise default to the whole name if match := re.search(r"\d.*", releaseName): releaseName = match[0] - # no yield here, we need to pin it - message = await self.room.send( - f'__[{event.data["repository"]["name"]} {releaseName}]({release["html_url"]})__' - ) + + yield f'__[{event.data["repository"]["name"]} {releaseName}]({release["html_url"]})__' if ( event.data["repository"]["name"] in self.publicConfig["importantRepositories"] ): - await self.room.pin(message) - yield "" # force it to be a generator + yield PinThat @wrap async def onFork(self, event: GitHubEvent): diff --git a/vyxalbot2/chat/reactions.py b/vyxalbot2/reactions.py similarity index 53% rename from vyxalbot2/chat/reactions.py rename to vyxalbot2/reactions.py index ab99098..410e2bc 100644 --- a/vyxalbot2/chat/reactions.py +++ b/vyxalbot2/reactions.py @@ -4,10 +4,8 @@ from itertools import chain, repeat -from sechat import EventType, MessageEvent, Room +from vyxalbot2.services import Service -if typing.TYPE_CHECKING: - from vyxalbot2.chat import Chat from vyxalbot2.types import EventInfo from vyxalbot2.types import MessagesType from vyxalbot2.util import RAPTOR @@ -36,16 +34,14 @@ ) class Reactions: - def __init__(self, room: Room, chat: "Chat", messages: MessagesType): - self.room = room - self.chat = chat + def __init__(self, messages: MessagesType): self.messages = messages - async def runCommand(self, name: str, event: MessageEvent, *args): - async for line in self.chat.commands[name](EventInfo(event.user_name, event.user_id, event.message_id), *args): - await self.room.send(line) + async def runCommand(self, service: Service, name: str, event: EventInfo, *args): + async for line in service.invokeCommand(name, event, *args): + await service.send(line) - async def onMessage(self, event: MessageEvent): + async def onMessage(self, service: Service, event: EventInfo): didSomething = False for regex, function in MESSAGE_REGEXES.items(): if function not in DO_NOT_IGNORE_COMMAND_PREFIX: @@ -53,40 +49,40 @@ async def onMessage(self, event: MessageEvent): else: reMatch = re.fullmatch(regex, event.content.lower()) if reMatch is not None: - if event.user_id == self.room.userID and function not in OK_TO_SELF_REPLY: + if event.userIdent == event.service.clientIdent and function not in OK_TO_SELF_REPLY: continue - await getattr(self, function)(event, reMatch) + await getattr(self, function)(service, event, reMatch) didSomething = True return didSomething - async def info(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("info", event) + async def info(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "info", event) - async def cookie(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("cookie", event) + async def cookie(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "cookie", event) - async def coffee(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("coffee", event) + async def coffee(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "coffee", event) - async def maul(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("maul", event, reMatch.group("user")) + async def maul(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "maul", event, reMatch.group("user")) - async def sus(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("sus", event) + async def sus(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "sus", event) - async def blame(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("blame", event) + async def blame(self, service: Service, event: EventInfo, reMatch: re.Match): + await self.runCommand(service, "blame", event) - async def goodBot(self, event: MessageEvent, reMatch: re.Match): - await self.room.send(":3") + async def goodBot(self, service: Service, event: EventInfo, reMatch: re.Match): + await service.send(":3") - async def hello(self, event: MessageEvent, reMatch: re.Match): - await self.room.send(random.choice(self.messages["hello"])) + async def hello(self, service: Service, event: EventInfo, reMatch: re.Match): + await service.send(random.choice(self.messages["hello"])) - async def goodbye(self, event: MessageEvent, reMatch: re.Match): - await self.room.send(random.choice(self.messages["goodbye"])) + async def goodbye(self, service: Service, event: EventInfo, reMatch: re.Match): + await service.send(random.choice(self.messages["goodbye"])) - async def mojo(self, event: MessageEvent, reMatch: re.Match): + async def mojo(self, service: Service, event: EventInfo, reMatch: re.Match): emojis = [ "".join(random.choices(("đŸ¤Ŗ", "😂"), weights=[12, 8], k=random.randint(3, 7))), "đŸ’¯" * random.choice((1, 3, 5)), @@ -94,4 +90,4 @@ async def mojo(self, event: MessageEvent, reMatch: re.Match): ] random.shuffle(emojis) emojis = "".join(emojis) + ("đŸ˜ŗ" * (random.randint(1, 10) == 1)) - await self.room.send(emojis) + await service.send(emojis) diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py new file mode 100644 index 0000000..b43bf7f --- /dev/null +++ b/vyxalbot2/services/__init__.py @@ -0,0 +1,32 @@ +from typing import Self, TYPE_CHECKING + +if TYPE_CHECKING: + from vyxalbot2.commands import CommandSupplier + from vyxalbot2.reactions import Reactions + from vyxalbot2.types import CommonData, EventInfo + +PinThat = object() + +class Service: + @classmethod + async def create(cls, reactions: "Reactions", common: "CommonData") -> Self: + raise NotImplementedError + + def __init__(self, name: str, clientIdent: int, commands: "CommandSupplier"): + self.name = name + self.clientIdent = clientIdent + self.commands = commands + + async def startup(self): + pass + async def shutdown(self): + pass + + def invokeCommand(self, name: str, event: "EventInfo", *args): + return self.commands.invoke(name, event, *args) + + async def send(self, message: str): + raise NotImplementedError + + async def pin(self, message: int): + raise NotImplementedError \ No newline at end of file diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py new file mode 100644 index 0000000..e944495 --- /dev/null +++ b/vyxalbot2/services/discord.py @@ -0,0 +1,110 @@ +from typing import Any, AsyncGenerator, Callable, Optional +from types import FunctionType +from asyncio import Event, get_event_loop + +import logging +import inspect + +from discord import Client, Intents, Interaction, Object +from discord.app_commands import CommandTree, Command as DiscordCommand, Group, Choice, choices +from vyxalbot2.commands import Command +from vyxalbot2.commands.discord import DiscordCommands +from vyxalbot2.services import Service +from vyxalbot2.reactions import Reactions +from vyxalbot2.types import CommandImpl, CommonData, EventInfo, PrivateConfigType + +class VBClient(Client): + def __init__(self, guild: int): + super().__init__(intents=Intents.all()) + self.guild = Object(guild) + self.tree = CommandTree(self) + + def wrap(self, service: "DiscordService", impl: CommandImpl): + # discord.py checks the signature of the wrapper to generate autocomplete, + # so we inject the wrapped function's signature into the wrapper via dark Python magicks + # do note: this operation does not actually change the signature of the function! + # it just make it look like it's changed to inspect and other machinery + # which would be bad in any other situation but this, although even here it's not great + # TL;DR I used the inspect to bamboozle the inspect + async def wrapper(interaction: Interaction, *args, **kwargs): + async for line in impl( + EventInfo( + "", # :( + interaction.user.display_name, + interaction.user.id, + interaction.id, + service + ), + *args, **kwargs + ): + await interaction.response.send_message(line) + + # 😰 + wrapSig = inspect.signature(wrapper) + wrapper.__signature__ = wrapSig.replace( + parameters=[wrapSig.parameters["interaction"], *tuple(inspect.signature(impl).parameters.values())[1:]] + ) + return wrapper + + def addCommand(self, service: "DiscordService", command: Command): + parts = command.name.split(" ") + parent = None + assert len(parts) > 0 + if len(parts) > 1: + part = parts.pop(0) + parent = self.tree.get_command(part) + if parent is None: + parent = Group(name=part, description="This seems to be a toplevel group of some kind.") + assert not isinstance(parent, DiscordCommand), "Cannot nest commands under commands" + while len(parts) > 1: + part = parts.pop(0) + newParent = parent.get_command(part) + if newParent is None: + newParent = Group( + name=part, + parent=parent, + description="This seems to be a group of some kind." + ) + parent = newParent + assert not isinstance(parent, DiscordCommand), "Cannot nest commands under commands" + self.tree.add_command(DiscordCommand( + name=parts[0], + description=command.helpStr, + callback=self.wrap(service, command.impl), + parent=parent + )) + async def setup_hook(self): + self.tree.copy_global_to(guild=self.guild) + +class DiscordService(Service): + @classmethod + async def create(cls, reactions: Reactions, common: CommonData): + client = VBClient(common.privateConfig["guild"]) + await client.login(common.privateConfig["discordToken"]) + instance = cls(client, reactions, common) + await instance.startup() + return instance + + def __init__(self, client: VBClient, reactions: Reactions, common: CommonData): + assert client.user is not None, "Need to be logged in to Discord!" + super().__init__("discord", client.user.id, DiscordCommands(common)) + + self.logger = logging.getLogger("DiscordService") + self.client = client + self.common = common + self.reactions = reactions + + for command in self.commands.commands.values(): + self.client.addCommand(self, command) + + async def startup(self): + self.clientTask = get_event_loop().create_task(self.client.connect()) + await self.client.wait_until_ready() + await self.client.tree.sync() + self.logger.info(f"Discord connection established! We are {self.client.user}.") + + async def shutdown(self): + self.clientTask.cancel() + await self.clientTask + + \ No newline at end of file diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py new file mode 100644 index 0000000..3d79b45 --- /dev/null +++ b/vyxalbot2/services/se/__init__.py @@ -0,0 +1,157 @@ +from asyncio import wait_for +import inspect +from time import time +from typing import Callable +from datetime import datetime +from string import ascii_letters + +import re +import random +import codecs +import base64 +import json +import subprocess + +from gidgethub import BadRequest, HTTPException as GitHubHTTPException, ValidationError +from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI +from aiohttp import ClientSession +from sechat import Bot, EventType +from tinydb.table import Document +from sechat.room import Room +from sechat.events import MessageEvent, EditEvent +from uwuivy import uwuipy + +import yaml +import logging +from vyxalbot2.commands.common import CommonCommands +from vyxalbot2.commands.se import SECommands + +from vyxalbot2.reactions import Reactions +from vyxalbot2.github import GitHubApplication +from vyxalbot2.services import PinThat, Service +from vyxalbot2.services.se.parser import CommandParser, ParseError +from vyxalbot2.types import CommonData, EventInfo, PrivateConfigType, PublicConfigType, MessagesType +from vyxalbot2.userdb import UserDB + +class SEService(Service): + @classmethod + async def create(cls, reactions: Reactions, common: CommonData): + bot = Bot() + await bot.authenticate( + common.privateConfig["chat"]["email"], + common.privateConfig["chat"]["password"], + common.privateConfig["chat"]["host"], + ) + room = await bot.joinRoom(common.privateConfig["chat"]["room"]) + instance = cls(bot, room, reactions, common) + await instance.startup() + return instance + + def __init__(self, bot: Bot, room: Room, reactions: Reactions, common: CommonData): + super().__init__("se", room.userID, SECommands(room, common, self)) + self.bot = bot + self.room = room + self.common = common + self.reactions = reactions + + self.logger = logging.getLogger("SEService") + self.logger.info(f"Connected to chat as user {room.userID}") + self.editDB: dict[int, tuple[datetime, list[int]]] = {} + self.parser = CommandParser(self.commands.commands) + + self.common.ghClient.services.append(self) + self.room.register(self.onMessage, EventType.MESSAGE) + self.room.register(self.onEdit, EventType.EDIT) + + async def startup(self): + await self.room.send( + "Well, here we are again." + if random.random() > 0.01 + else "GOOD MORNING, MOTHERF***ERS" + ) + + async def shutdown(self): + await self.bot.shutdown() + + async def onMessage(self, room: Room, message: MessageEvent): + event = EventInfo( + message.content, + message.user_name, + message.user_id, + message.message_id, + self + ) + if await self.reactions.onMessage(self, event): + # A reaction ran, so don't get pissy about invalid commands + return + if message.user_id == self.room.userID: + return + if not message.content.startswith("!!/"): + return + sentAt = datetime.now() + response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), event)] + if not len(response): + return + responseIDs = [await self.room.reply(message.message_id, response[0])] + for line in response[1:]: + if line == PinThat: + await self.room.pin(responseIDs[-1]) + continue + responseIDs.append(await self.room.send(line)) + self.editDB[message.message_id] = (sentAt, responseIDs) + + async def onEdit(self, room: Room, edit: EditEvent): + if edit.user_id == self.room.userID: + return + if not edit.content.startswith("!!/"): + return + if edit.message_id not in self.editDB: + await self.onMessage(room, edit) + else: + event = EventInfo( + edit.content, + edit.user_name, + edit.user_id, + edit.message_id, + self + ) + sentAt, idents = self.editDB[edit.message_id] + if (datetime.now() - sentAt).seconds > (60 * 2): # margin of error + await self.onMessage(room, edit) + return + response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), event)] + if len(response): + response[0] = f":{edit.message_id} " + response[0] + for x in range(min(len(idents), len(response))): + await self.room.edit(idents.pop(0), response.pop(0)) + for leftover in response: + await self.room.send(leftover) + for leftover in idents: + await self.room.delete(leftover) + self.editDB.pop(edit.message_id) + for key, value in self.editDB.copy().items(): + if (datetime.now() - value[0]).seconds > (60 * 2): + self.editDB.pop(key) + + async def processMessage(self, message: str, event: EventInfo): + try: + commandName, impl, args = self.parser.parseCommand(message) + except ParseError as e: + yield "Command error: " + e.message + return + userInfo = await self.common.userDB.getUser(self, event.userIdent) + for groupName, group in self.common.publicConfig["groups"].items(): + if commandName in group.get("canRun", []): + if userInfo is not None: + if groupName not in userInfo.groups: + yield f"Only members of group {groupName} can run !!/{commandName}." + return + else: + yield f"Only members of group {groupName} can run !!/{commandName}." + return + try: + async for l in impl(event, *args): + yield l + except Exception as e: + yield f"@Ginger An exception occured whilst processing this message!" + self.logger.exception(f"An exception occured whilst processing message {event.messageIdent}:") diff --git a/vyxalbot2/chat/parser.py b/vyxalbot2/services/se/parser.py similarity index 87% rename from vyxalbot2/chat/parser.py rename to vyxalbot2/services/se/parser.py index 41341a2..9d6eb25 100644 --- a/vyxalbot2/chat/parser.py +++ b/vyxalbot2/services/se/parser.py @@ -7,6 +7,8 @@ from sechat.room import Room from sechat.events import MessageEvent +from vyxalbot2.commands import Command + class ParseState(Enum): TOPLEVEL = auto() FLAG = auto() @@ -35,7 +37,7 @@ def __init__(self, message: str): self.message = message class CommandParser: - def __init__(self, commands: dict[str, Callable]): + def __init__(self, commands: dict[str, Command]): self.commands = commands def parseArgs(self, args: str): @@ -154,11 +156,14 @@ def parseCommand(self, command: str): if ty != TokenType.FLAG: raise ParseError(f"Expected command name, got {ty.name}") assert isinstance(commandName, str) - while len(args) and args[0][0] == TokenType.FLAG: - assert isinstance((i := args.pop(0)[1]), str) - commandName += " " + i + if commandName not in self.commands: + while len(args) and args[0][0] == TokenType.FLAG: + assert isinstance((i := args.pop(0)[1]), str) + commandName += " " + i + if commandName in self.commands: + break try: - impl = self.commands[commandName] + impl = self.commands[commandName].impl except KeyError: maybeYouMeant = [] for command in self.commands.keys(): @@ -171,7 +176,10 @@ def parseCommand(self, command: str): for paramName, param in signature(impl).parameters.items(): if paramName in ("event", "self"): continue - paramType = TYPES_TO_TOKENS[param.annotation] + if issubclass(param.annotation, Enum): + paramType = TokenType.FLAG + else: + paramType = TYPES_TO_TOKENS[param.annotation] try: argType, argValue = args.pop(0) except IndexError: @@ -182,10 +190,15 @@ def parseCommand(self, command: str): else: if argType == TokenType.ERROR: raise ParseError(str(argValue)) - if argType == TokenType.FLAG: - argType = TokenType.STRING if argType != paramType: raise ParseError(f"Expected {paramType.name} for {paramName} but got {argType.name}") - argValues.append(argValue) + if argType == TokenType.FLAG: + assert issubclass(param.annotation, Enum) + try: + argValues.append(param.annotation(argValue)) + except ValueError: + raise ParseError(f"Invalid value for {paramName}! Expected one of: {', '.join(member.value for member in param.annotation)}") + else: + argValues.append(argValue) return commandName, impl, argValues diff --git a/vyxalbot2/types.py b/vyxalbot2/types.py index 9183c0f..b704dd1 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -1,12 +1,20 @@ -from typing import Optional, TypedDict +from typing import Any, AsyncGenerator, Callable, Optional, TypedDict, TYPE_CHECKING +from enum import Enum, auto from datetime import datetime from dataclasses import dataclass +if TYPE_CHECKING: + from vyxalbot2.services import Service + from vyxalbot2.github import GitHubApplication + from vyxalbot2.userdb import UserDB + +CommandImpl = Callable[..., AsyncGenerator[Any, None]] class GroupType(TypedDict, total=False): promotionRequires: list[str] canRun: list[str] - protected: list[int] + protected: dict[str, list[int]] + linkedRole: int class ProductionType(TypedDict): @@ -31,6 +39,12 @@ class PrivateConfigType(TypedDict): webhookSecret: str tyxalInstance: str + mongoUrl: str + database: str + + discordToken: str + guild: int + chat: ChatConfigType class AutotagType(TypedDict): @@ -69,9 +83,21 @@ class AppToken: token: str expires: datetime +@dataclass +class CommonData: + statuses: list[str] + messages: MessagesType + publicConfig: PublicConfigType + privateConfig: PrivateConfigType + errorsSinceStartup: int + startupTime: datetime + userDB: "UserDB" + ghClient: "GitHubApplication" @dataclass class EventInfo: + content: str userName: str userIdent: int messageIdent: int + service: "Service" \ No newline at end of file diff --git a/vyxalbot2/userdb.py b/vyxalbot2/userdb.py index 638db7c..7d16528 100644 --- a/vyxalbot2/userdb.py +++ b/vyxalbot2/userdb.py @@ -1,12 +1,24 @@ from typing import Optional +from blinker import Signal from tinydb import TinyDB, Query from tinydb.table import Document +from motor.motor_asyncio import AsyncIOMotorClient +from odmantic import AIOEngine, EmbeddedModel, Model, ObjectId +from vyxalbot2.services import Service + from vyxalbot2.types import GroupType +class User(Model): + service: str + serviceIdent: int + name: str + pfp: str + groups: list[str] = [] + linked: dict[str, ObjectId] = {} -class UserDB: +class UserDBOld: def __init__(self, dbPath: str, groupConfig: dict[str, GroupType]): self._db = TinyDB(dbPath) self.groupConfig = groupConfig @@ -44,3 +56,35 @@ def membersOfGroup(self, group: str): def users(self): return self._db.all() + +class UserDB: + userModify = Signal() + def __init__(self, client, database: str): + self.engine = AIOEngine(client=client, database=database) + + async def getUser(self, service: Service, ident: int) -> Optional[User]: + return await self.engine.find_one(User, User.service == service.name, User.serviceIdent == ident) + + async def getUsers(self, service: Service): + return await self.engine.find(User, User.service == service.name) + + async def getUserByName(self, service: Service, name: str) -> Optional[User]: + return await self.engine.find_one(User, User.service == service.name, User.name == name) + + async def createUser(self, service: Service, ident: int, name: str, pfp: str): + if (await self.getUser(service, ident)) is not None: + raise ValueError("User exists") + await self.save(User(service=service.name, serviceIdent=ident, name=name, pfp=pfp)) + + async def linkUser(self, one: User, other: User): + one.linked[other.service] = other.id + other.linked[one.service] = one.id + await self.save(one) + await self.save(other) + + async def membersOfGroup(self, service: Service, group: str): + return await self.engine.find(User, User.service == service.name, {"groups": group}) + + async def save(self, user: User): + await self.engine.save(user) + await self.userModify.send_async(self) \ No newline at end of file From b4f48cf948aedca0fa34244d4da4f4c0498bbf58 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:30:38 +0000 Subject: [PATCH 02/42] Set up GH event reporting --- vyxalbot2/__init__.py | 2 ++ vyxalbot2/github/__init__.py | 6 +++++- vyxalbot2/services/__init__.py | 2 +- vyxalbot2/services/discord.py | 16 ++++++++++++---- vyxalbot2/services/se/__init__.py | 8 +++++++- vyxalbot2/types.py | 8 +++++--- 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index 145877a..e87deb9 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -84,6 +84,8 @@ async def run(self): reactions = Reactions(self.messages) self.se = await SEService.create(reactions, common) self.discord = await DiscordService.create(reactions, common) + ghApp.services.append(self.se) + ghApp.services.append(self.discord) ghApp.on_shutdown.append(self.shutdown) return ghApp diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index 0fbcd27..4709775 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -21,8 +21,12 @@ def wrap(fun): async def wrapper(self: "GitHubApplication", service: Service, event: GitHubEvent, gh: AsyncioGitHubAPI): + ids = [] async for line in fun(self, event): - await service.send(line) + if line == PinThat: + await service.pin(ids[-1]) + continue + ids.append(await service.send(line)) return wrapper class GitHubApplication(Application): diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py index b43bf7f..f80b65c 100644 --- a/vyxalbot2/services/__init__.py +++ b/vyxalbot2/services/__init__.py @@ -25,7 +25,7 @@ async def shutdown(self): def invokeCommand(self, name: str, event: "EventInfo", *args): return self.commands.invoke(name, event, *args) - async def send(self, message: str): + async def send(self, message: str) -> int: raise NotImplementedError async def pin(self, message: int): diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index e944495..f7d1a20 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -5,7 +5,7 @@ import logging import inspect -from discord import Client, Intents, Interaction, Object +from discord import Client, Intents, Interaction, Object, TextChannel from discord.app_commands import CommandTree, Command as DiscordCommand, Group, Choice, choices from vyxalbot2.commands import Command from vyxalbot2.commands.discord import DiscordCommands @@ -79,8 +79,8 @@ async def setup_hook(self): class DiscordService(Service): @classmethod async def create(cls, reactions: Reactions, common: CommonData): - client = VBClient(common.privateConfig["guild"]) - await client.login(common.privateConfig["discordToken"]) + client = VBClient(common.privateConfig["discord"]["guild"]) + await client.login(common.privateConfig["discord"]["token"]) instance = cls(client, reactions, common) await instance.startup() return instance @@ -94,6 +94,10 @@ def __init__(self, client: VBClient, reactions: Reactions, common: CommonData): self.common = common self.reactions = reactions + eventChannel = self.client.get_channel(self.common.privateConfig["discord"]["eventChannel"]) + assert isinstance(eventChannel, TextChannel) + self.eventChannel = eventChannel + for command in self.commands.commands.values(): self.client.addCommand(self, command) @@ -107,4 +111,8 @@ async def shutdown(self): self.clientTask.cancel() await self.clientTask - \ No newline at end of file + async def send(self, message: str): + return (await self.eventChannel.send(message)).id + + async def pin(self, message: int): + await self.eventChannel.get_partial_message(message).pin() \ No newline at end of file diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 3d79b45..b569903 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -73,6 +73,12 @@ async def startup(self): async def shutdown(self): await self.bot.shutdown() + async def send(self, message: str): + return await self.room.send(message) + + async def pin(self, message: int): + await self.room.pin(message) + async def onMessage(self, room: Room, message: MessageEvent): event = EventInfo( message.content, @@ -154,4 +160,4 @@ async def processMessage(self, message: str, event: EventInfo): yield l except Exception as e: yield f"@Ginger An exception occured whilst processing this message!" - self.logger.exception(f"An exception occured whilst processing message {event.messageIdent}:") + self.logger.exception(f"An exception occured whilst processing message {event.messageIdent}:") \ No newline at end of file diff --git a/vyxalbot2/types.py b/vyxalbot2/types.py index b704dd1..3a07ecc 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -28,6 +28,10 @@ class ChatConfigType(TypedDict): email: str password: str +class DiscordConfigType(TypedDict): + token: str + guild: int + eventChannel: int class PrivateConfigType(TypedDict): port: int @@ -42,10 +46,8 @@ class PrivateConfigType(TypedDict): mongoUrl: str database: str - discordToken: str - guild: int - chat: ChatConfigType + discord: DiscordConfigType class AutotagType(TypedDict): issue2pr: dict[str, str] From 21cfacb372012fee3fb440ee3e93995e74974db0 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 03:55:29 +0000 Subject: [PATCH 03/42] Get bridge working --- vyxalbot2/__init__.py | 5 +++- vyxalbot2/bridge.py | 47 +++++++++++++++++++++++++++++++ vyxalbot2/commands/se.py | 18 ++---------- vyxalbot2/services/__init__.py | 4 +++ vyxalbot2/services/discord.py | 25 ++++++++++++---- vyxalbot2/services/se/__init__.py | 36 +++++++++++++++++------ vyxalbot2/types.py | 3 ++ vyxalbot2/userdb.py | 1 + vyxalbot2/util.py | 14 +++++++++ 9 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 vyxalbot2/bridge.py diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index e87deb9..91e3ad3 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -35,6 +35,7 @@ from uwuivy import uwuipy from discord.utils import setup_logging from motor.motor_asyncio import AsyncIOMotorClient +from vyxalbot2.bridge import DiscordBridge from vyxalbot2.commands.common import CommonCommands from vyxalbot2.github import GitHubApplication @@ -70,6 +71,7 @@ async def run(self): userDB = UserDB(AsyncIOMotorClient(self.privateConfig["mongoUrl"]), self.privateConfig["database"]) ghApp = GitHubApplication(self.publicConfig, self.privkey, self.privateConfig["appID"], self.privateConfig["account"], self.privateConfig["webhookSecret"]) + reactions = Reactions(self.messages) common = CommonData( self.statuses, @@ -81,9 +83,10 @@ async def run(self): userDB, ghApp ) - reactions = Reactions(self.messages) self.se = await SEService.create(reactions, common) self.discord = await DiscordService.create(reactions, common) + bridge = DiscordBridge(self.se, self.discord, self.discord.client.guilds[0], common) + await bridge.start() ghApp.services.append(self.se) ghApp.services.append(self.discord) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py new file mode 100644 index 0000000..737d2dd --- /dev/null +++ b/vyxalbot2/bridge.py @@ -0,0 +1,47 @@ +from logging import getLogger +from aiohttp import ClientSession +from discord import Client, Guild, TextChannel +from vyxalbot2.services import Service +from vyxalbot2.services.discord import DiscordService +from vyxalbot2.types import CommonData, EventInfo +from vyxalbot2.userdb import User + + +class DiscordBridge: + def __init__(self, source: Service, discord: DiscordService, guild: Guild, common: CommonData): + self.logger = getLogger("DiscordBridge") + self.source = source + self.discord = discord + self.guild = guild + self.common = common + + self.webhook = None + + channel = self.guild.get_channel(common.privateConfig["discord"]["bridgeChannel"]) + assert isinstance(channel, TextChannel) + self.channel = channel + source.messageSignal.connect(self.onMessage, source, False) + discord.messageSignal.connect(self.onDiscordMessage, discord, False) + + async def fetchPFP(self, pfp: str): + async with ClientSession() as session: + async with session.get(pfp) as response: + return await response.content.read() + + async def start(self): + self.webhook = (await self.channel.webhooks())[0] + self.logger.info("Ready!") + + async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): + if directedAtUs: + return + assert self.webhook is not None + await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) + + async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): + assert self.webhook is not None + if event.roomIdent != self.channel.id: + return + if event.userIdent == self.webhook.id: + return + await self.source.send(f"◈ [{event.userName}] " + event.content) \ No newline at end of file diff --git a/vyxalbot2/commands/se.py b/vyxalbot2/commands/se.py index b4f5bd3..598fb32 100644 --- a/vyxalbot2/commands/se.py +++ b/vyxalbot2/commands/se.py @@ -15,10 +15,7 @@ from vyxalbot2.userdb import User from urllib.parse import urlparse, urlunparse -from vyxalbot2.util import TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage - -STACK_IMGUR = "i.stack.imgur.com" -DEFAULT_PFP = "https://cdn-chat.sstatic.net/chat/img/anon.png" +from vyxalbot2.util import TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage, resolveChatPFP class SECommands(CommonCommands): def __init__(self, room: Room, common: CommonData, service: "SEService"): @@ -39,15 +36,6 @@ def genHelpStrings(self): help[baseName] = [command.fullHelp] return help - def resolveChatPFP(self, pfp: str): - if pfp.startswith("!"): - pfp = pfp.removeprefix("!") - url = urlparse(pfp) - if url.netloc == STACK_IMGUR: - return urlunparse(("https", STACK_IMGUR, url.path, "", "s=256", "")) - return pfp - return f"https://www.gravatar.com/avatar/{pfp}?s=256&d=identicon&r=PG" - async def helpCommand(self, event: EventInfo, command: str = ""): """Provide help for a command.""" if command: @@ -137,7 +125,7 @@ async def registerCommand(self, event: EventInfo): self.service, thumb["id"], thumb["name"], - self.resolveChatPFP(thumb["email_hash"]) + resolveChatPFP(thumb["email_hash"]) ) yield "You have been registered! You don't have any permisssions yet." @@ -153,7 +141,7 @@ async def refreshCommand(self, event: EventInfo): ) as response: thumb = await response.json() user.name = thumb["name"] - user.pfp = self.resolveChatPFP(thumb["email_hash"]) + user.pfp = resolveChatPFP(thumb["email_hash"]) await self.userDB.save(user) yield "Your details have been updated." diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py index f80b65c..35399e5 100644 --- a/vyxalbot2/services/__init__.py +++ b/vyxalbot2/services/__init__.py @@ -1,5 +1,7 @@ from typing import Self, TYPE_CHECKING +from blinker import Namespace, Signal + if TYPE_CHECKING: from vyxalbot2.commands import CommandSupplier from vyxalbot2.reactions import Reactions @@ -8,6 +10,8 @@ PinThat = object() class Service: + messageSignal = Signal() + editSignal = Signal() @classmethod async def create(cls, reactions: "Reactions", common: "CommonData") -> Self: raise NotImplementedError diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index f7d1a20..4b0c82a 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -5,7 +5,7 @@ import logging import inspect -from discord import Client, Intents, Interaction, Object, TextChannel +from discord import Client, Intents, Interaction, Message, Object, TextChannel from discord.app_commands import CommandTree, Command as DiscordCommand, Group, Choice, choices from vyxalbot2.commands import Command from vyxalbot2.commands.discord import DiscordCommands @@ -27,11 +27,14 @@ def wrap(self, service: "DiscordService", impl: CommandImpl): # which would be bad in any other situation but this, although even here it's not great # TL;DR I used the inspect to bamboozle the inspect async def wrapper(interaction: Interaction, *args, **kwargs): + assert interaction.channel_id is not None async for line in impl( EventInfo( "", # :( interaction.user.display_name, + interaction.user.display_avatar.url, interaction.user.id, + interaction.channel_id, interaction.id, service ), @@ -76,6 +79,7 @@ def addCommand(self, service: "DiscordService", command: Command): async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) + class DiscordService(Service): @classmethod async def create(cls, reactions: Reactions, common: CommonData): @@ -91,13 +95,10 @@ def __init__(self, client: VBClient, reactions: Reactions, common: CommonData): self.logger = logging.getLogger("DiscordService") self.client = client + self.client.event(self.on_message) self.common = common self.reactions = reactions - eventChannel = self.client.get_channel(self.common.privateConfig["discord"]["eventChannel"]) - assert isinstance(eventChannel, TextChannel) - self.eventChannel = eventChannel - for command in self.commands.commands.values(): self.client.addCommand(self, command) @@ -105,8 +106,22 @@ async def startup(self): self.clientTask = get_event_loop().create_task(self.client.connect()) await self.client.wait_until_ready() await self.client.tree.sync() + eventChannel = self.client.get_channel(self.common.privateConfig["discord"]["eventChannel"]) + assert isinstance(eventChannel, TextChannel), str(eventChannel) + self.eventChannel = eventChannel self.logger.info(f"Discord connection established! We are {self.client.user}.") + async def on_message(self, message: Message): + await self.messageSignal.send_async(self, event=EventInfo( + content=message.content, + userName=message.author.display_name, + pfp=message.author.display_avatar.url, + roomIdent=message.channel.id, + userIdent=message.author.id, + messageIdent=message.id, + service=self + )) + async def shutdown(self): self.clientTask.cancel() await self.clientTask diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index b569903..2b51991 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -11,6 +11,7 @@ import base64 import json import subprocess +from discord import User from gidgethub import BadRequest, HTTPException as GitHubHTTPException, ValidationError from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI @@ -32,6 +33,7 @@ from vyxalbot2.services.se.parser import CommandParser, ParseError from vyxalbot2.types import CommonData, EventInfo, PrivateConfigType, PublicConfigType, MessagesType from vyxalbot2.userdb import UserDB +from vyxalbot2.util import resolveChatPFP class SEService(Service): @classmethod @@ -54,6 +56,8 @@ def __init__(self, bot: Bot, room: Room, reactions: Reactions, common: CommonDat self.common = common self.reactions = reactions + self.pfpCache: dict[int, str] = {} + self.logger = logging.getLogger("SEService") self.logger.info(f"Connected to chat as user {room.userID}") self.editDB: dict[int, tuple[datetime, list[int]]] = {} @@ -79,11 +83,22 @@ async def send(self, message: str): async def pin(self, message: int): await self.room.pin(message) + async def getPFP(self, user: int): + if user not in self.pfpCache: + async with ClientSession() as session: + async with session.get( + f"https://chat.stackexchange.com/users/thumbs/{user}" + ) as response: + self.pfpCache[user] = resolveChatPFP((await response.json())["email_hash"]) + return self.pfpCache[user] + async def onMessage(self, room: Room, message: MessageEvent): event = EventInfo( message.content, message.user_name, + await self.getPFP(message.user_id), message.user_id, + message.room_id, message.message_id, self ) @@ -92,6 +107,7 @@ async def onMessage(self, room: Room, message: MessageEvent): return if message.user_id == self.room.userID: return + await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if not message.content.startswith("!!/"): return sentAt = datetime.now() @@ -107,23 +123,27 @@ async def onMessage(self, room: Room, message: MessageEvent): self.editDB[message.message_id] = (sentAt, responseIDs) async def onEdit(self, room: Room, edit: EditEvent): + event = EventInfo( + edit.content, + edit.user_name, + await self.getPFP(edit.user_id), + edit.user_id, + edit.room_id, + edit.message_id, + self + ) if edit.user_id == self.room.userID: return + await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if not edit.content.startswith("!!/"): return if edit.message_id not in self.editDB: await self.onMessage(room, edit) else: - event = EventInfo( - edit.content, - edit.user_name, - edit.user_id, - edit.message_id, - self - ) sentAt, idents = self.editDB[edit.message_id] if (datetime.now() - sentAt).seconds > (60 * 2): # margin of error - await self.onMessage(room, edit) + with self.messageSignal.muted(): + await self.onMessage(room, edit) return response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), event)] if len(response): diff --git a/vyxalbot2/types.py b/vyxalbot2/types.py index 3a07ecc..3b49654 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -32,6 +32,7 @@ class DiscordConfigType(TypedDict): token: str guild: int eventChannel: int + bridgeChannel: int class PrivateConfigType(TypedDict): port: int @@ -100,6 +101,8 @@ class CommonData: class EventInfo: content: str userName: str + pfp: str + roomIdent: int userIdent: int messageIdent: int service: "Service" \ No newline at end of file diff --git a/vyxalbot2/userdb.py b/vyxalbot2/userdb.py index 7d16528..f4404ab 100644 --- a/vyxalbot2/userdb.py +++ b/vyxalbot2/userdb.py @@ -17,6 +17,7 @@ class User(Model): pfp: str groups: list[str] = [] linked: dict[str, ObjectId] = {} + bonusData: dict[str, str] = {} class UserDBOld: def __init__(self, dbPath: str, groupConfig: dict[str, GroupType]): diff --git a/vyxalbot2/util.py b/vyxalbot2/util.py index ad933ba..3ec71bc 100644 --- a/vyxalbot2/util.py +++ b/vyxalbot2/util.py @@ -1,14 +1,28 @@ import re +from urllib.parse import urlparse, urlunparse from aiohttp import ClientSession import bs4 + GITHUB_MERGE_QUEUE = "github-merge-queue[bot]" TRASH = 82806 LINK_REGEX = r"https?://chat.stackexchange.com/transcript(/message)?/(?P\d+)(#.*)?" +STACK_IMGUR = "i.stack.imgur.com" +DEFAULT_PFP = "https://cdn-chat.sstatic.net/chat/img/anon.png" + +def resolveChatPFP(pfp: str): + if pfp.startswith("!"): + pfp = pfp.removeprefix("!") + url = urlparse(pfp) + if url.netloc == STACK_IMGUR: + return urlunparse(("https", STACK_IMGUR, url.path, "", "s=256", "")) + return pfp + return f"https://www.gravatar.com/avatar/{pfp}?s=256&d=identicon&r=PG" + def extractMessageIdent(ident: str): if ident.isdigit(): return int(ident) From b35da0446a407cde3efdbabc3ade6e57270e1a10 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:00:23 +0000 Subject: [PATCH 04/42] fixen --- vyxalbot2/github/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index 4709775..8a0bdbf 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -20,13 +20,15 @@ from vyxalbot2.util import GITHUB_MERGE_QUEUE def wrap(fun): - async def wrapper(self: "GitHubApplication", service: Service, event: GitHubEvent, gh: AsyncioGitHubAPI): - ids = [] - async for line in fun(self, event): - if line == PinThat: - await service.pin(ids[-1]) - continue - ids.append(await service.send(line)) + async def wrapper(self: "GitHubApplication", event: GitHubEvent, services: list[Service], gh: AsyncioGitHubAPI): + lines = [i async for i in fun(self, event)] + for service in services: + ids = [] + for line in lines: + if line == PinThat: + await service.pin(ids[-1]) + continue + ids.append(await service.send(line)) return wrapper class GitHubApplication(Application): @@ -105,7 +107,7 @@ async def onHookRequest(self, request: Request) -> Response: return Response(status=200) if repo["name"] in self.publicConfig["ignoredRepositories"]: return Response(status=200) - await self.ghRouter.dispatch(event, self.gh) + await self.ghRouter.dispatch(event, self.services, self.gh) return Response(status=200) except Exception: if event: From 9cafdef15ce462c78a01e1a43539bb33be067a29 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:05:50 +0000 Subject: [PATCH 05/42] Make bot ignore self --- vyxalbot2/bridge.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index 737d2dd..fffb875 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -39,9 +39,12 @@ async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): + assert self.discord.client.user is not None assert self.webhook is not None if event.roomIdent != self.channel.id: return + if event.userIdent == self.discord.client.user.id: + return if event.userIdent == self.webhook.id: return await self.source.send(f"◈ [{event.userName}] " + event.content) \ No newline at end of file From 91c3f4514325a325259e5ee08ae587d2940ce0c1 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:15:46 +0000 Subject: [PATCH 06/42] funny status --- vyxalbot2/services/discord.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 4b0c82a..9469aa4 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -1,22 +1,23 @@ -from typing import Any, AsyncGenerator, Callable, Optional -from types import FunctionType -from asyncio import Event, get_event_loop +import random +from asyncio import get_event_loop import logging import inspect -from discord import Client, Intents, Interaction, Message, Object, TextChannel -from discord.app_commands import CommandTree, Command as DiscordCommand, Group, Choice, choices +from discord import Client, CustomActivity, Intents, Interaction, Message, Object, TextChannel +from discord.app_commands import CommandTree, Command as DiscordCommand, Group +from discord.ext.tasks import loop from vyxalbot2.commands import Command from vyxalbot2.commands.discord import DiscordCommands from vyxalbot2.services import Service from vyxalbot2.reactions import Reactions -from vyxalbot2.types import CommandImpl, CommonData, EventInfo, PrivateConfigType +from vyxalbot2.types import CommandImpl, CommonData, EventInfo class VBClient(Client): - def __init__(self, guild: int): + def __init__(self, guild: int, statuses: list[str]): super().__init__(intents=Intents.all()) self.guild = Object(guild) + self.statuses = statuses self.tree = CommandTree(self) def wrap(self, service: "DiscordService", impl: CommandImpl): @@ -78,12 +79,17 @@ def addCommand(self, service: "DiscordService", command: Command): )) async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) + self.updateStatus.start() + + @loop(hours=1) + async def updateStatus(self): + await self.change_presence(activity=CustomActivity(name=random.choice(self.statuses))) class DiscordService(Service): @classmethod async def create(cls, reactions: Reactions, common: CommonData): - client = VBClient(common.privateConfig["discord"]["guild"]) + client = VBClient(common.privateConfig["discord"]["guild"], common.statuses) await client.login(common.privateConfig["discord"]["token"]) instance = cls(client, reactions, common) await instance.startup() From 4e50b7102529adc7e518ef01a28eeb31c30e0956 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:16:32 +0000 Subject: [PATCH 07/42] h --- vyxalbot2/services/discord.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 9469aa4..2537bca 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -79,6 +79,7 @@ def addCommand(self, service: "DiscordService", command: Command): )) async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) + await self.change_presence(activity=CustomActivity(name=random.choice(self.statuses))) self.updateStatus.start() @loop(hours=1) From 527b68f884280ba0844ae5da50272650600697d2 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:19:34 +0000 Subject: [PATCH 08/42] attempt 3 --- vyxalbot2/services/discord.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 2537bca..aed3bbb 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -4,7 +4,7 @@ import logging import inspect -from discord import Client, CustomActivity, Intents, Interaction, Message, Object, TextChannel +from discord import Client, CustomActivity, Game, Intents, Interaction, Message, Object, TextChannel from discord.app_commands import CommandTree, Command as DiscordCommand, Group from discord.ext.tasks import loop from vyxalbot2.commands import Command @@ -79,12 +79,11 @@ def addCommand(self, service: "DiscordService", command: Command): )) async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) - await self.change_presence(activity=CustomActivity(name=random.choice(self.statuses))) self.updateStatus.start() @loop(hours=1) async def updateStatus(self): - await self.change_presence(activity=CustomActivity(name=random.choice(self.statuses))) + await self.change_presence(activity=Game(name=random.choice(self.statuses))) class DiscordService(Service): From 62d8bb561abcc686d96e56a6a2b991e04cc5413c Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:20:44 +0000 Subject: [PATCH 09/42] attempt 4 --- vyxalbot2/services/discord.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index aed3bbb..554a00e 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -79,6 +79,7 @@ def addCommand(self, service: "DiscordService", command: Command): )) async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) + await self.change_presence(activity=Game(name=random.choice(self.statuses))) self.updateStatus.start() @loop(hours=1) From f82e5332ce33ef7d4c5738288cc4fde855f9745a Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:22:54 +0000 Subject: [PATCH 10/42] attempt 5 --- vyxalbot2/services/discord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 554a00e..7b4a8eb 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -79,11 +79,11 @@ def addCommand(self, service: "DiscordService", command: Command): )) async def setup_hook(self): self.tree.copy_global_to(guild=self.guild) - await self.change_presence(activity=Game(name=random.choice(self.statuses))) self.updateStatus.start() @loop(hours=1) async def updateStatus(self): + await self.wait_until_ready() await self.change_presence(activity=Game(name=random.choice(self.statuses))) From 6889addc5d266f24ce5f5051c1e41f0486a18081 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:27:05 +0000 Subject: [PATCH 11/42] oops --- vyxalbot2/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyxalbot2/commands/__init__.py b/vyxalbot2/commands/__init__.py index 49a2a46..239f8a3 100644 --- a/vyxalbot2/commands/__init__.py +++ b/vyxalbot2/commands/__init__.py @@ -54,7 +54,7 @@ def genCommands(self): if not attr.__name__.lower().endswith("command"): continue if attr.__doc__ is None: - continue + attr.__doc__ = "â€Ļ" name = re.sub(r"([A-Z])", lambda match: " " + match.group(0).lower(), attr.__name__.removesuffix("Command")) commands[name] = Command(name, attr.__doc__, attr) From 591ebbafd6f76b0ca2499902881ef413175e0fa2 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:28:03 +0000 Subject: [PATCH 12/42] oops 2 --- vyxalbot2/commands/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/commands/__init__.py b/vyxalbot2/commands/__init__.py index 239f8a3..b6a810d 100644 --- a/vyxalbot2/commands/__init__.py +++ b/vyxalbot2/commands/__init__.py @@ -54,8 +54,10 @@ def genCommands(self): if not attr.__name__.lower().endswith("command"): continue if attr.__doc__ is None: - attr.__doc__ = "â€Ļ" + doc = "â€Ļ" + else: + doc = attr.__doc__ name = re.sub(r"([A-Z])", lambda match: " " + match.group(0).lower(), attr.__name__.removesuffix("Command")) - commands[name] = Command(name, attr.__doc__, attr) + commands[name] = Command(name, doc, attr) return commands \ No newline at end of file From 1551c354e826a4b85cb31ca1e6d56122d4ccd938 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:31:26 +0000 Subject: [PATCH 13/42] Self-forward on SE side --- vyxalbot2/bridge.py | 4 ++-- vyxalbot2/services/se/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index fffb875..09c934c 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -33,8 +33,8 @@ async def start(self): self.logger.info("Ready!") async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): - if directedAtUs: - return + # if directedAtUs: + # return assert self.webhook is not None await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 2b51991..52b68bb 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -105,9 +105,9 @@ async def onMessage(self, room: Room, message: MessageEvent): if await self.reactions.onMessage(self, event): # A reaction ran, so don't get pissy about invalid commands return + await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if message.user_id == self.room.userID: return - await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if not message.content.startswith("!!/"): return sentAt = datetime.now() @@ -132,9 +132,9 @@ async def onEdit(self, room: Room, edit: EditEvent): edit.message_id, self ) + await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if edit.user_id == self.room.userID: return - await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if not edit.content.startswith("!!/"): return if edit.message_id not in self.editDB: From 8da659d32e29f283bd630a7631205dda5d715f53 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 04:34:07 +0000 Subject: [PATCH 14/42] big brain time --- vyxalbot2/services/se/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 52b68bb..88a1b08 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -105,6 +105,8 @@ async def onMessage(self, room: Room, message: MessageEvent): if await self.reactions.onMessage(self, event): # A reaction ran, so don't get pissy about invalid commands return + if message.user_id == self.room.userID and message.content.startswith("◈"): + return await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if message.user_id == self.room.userID: return @@ -132,6 +134,8 @@ async def onEdit(self, room: Room, edit: EditEvent): edit.message_id, self ) + if edit.user_id == self.room.userID and edit.content.startswith("◈"): + return await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if edit.user_id == self.room.userID: return From d5dcd13f51c57c98289f9e158ab1365c49bb5065 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 12:30:06 +0000 Subject: [PATCH 15/42] Fix bot losing its mind because of bad chat forwarding implementation --- vyxalbot2/bridge.py | 9 +++++-- vyxalbot2/services/__init__.py | 2 ++ vyxalbot2/services/se/__init__.py | 42 +++++++++++++++++-------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index 09c934c..42dc686 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -21,6 +21,8 @@ def __init__(self, source: Service, discord: DiscordService, guild: Guild, commo assert isinstance(channel, TextChannel) self.channel = channel source.messageSignal.connect(self.onMessage, source, False) + source.commandRequestSignal.connect(self.onMessage, source, False) + source.commandResponseSignal.connect(self.onCommandResponse, source, False) discord.messageSignal.connect(self.onDiscordMessage, discord, False) async def fetchPFP(self, pfp: str): @@ -32,9 +34,12 @@ async def start(self): self.webhook = (await self.channel.webhooks())[0] self.logger.info("Ready!") + async def onCommandResponse(self, sender, line: str): + await self.channel.send(line) + async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): - # if directedAtUs: - # return + if directedAtUs: + return assert self.webhook is not None await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py index 35399e5..0877b9a 100644 --- a/vyxalbot2/services/__init__.py +++ b/vyxalbot2/services/__init__.py @@ -12,6 +12,8 @@ class Service: messageSignal = Signal() editSignal = Signal() + commandRequestSignal = Signal() + commandResponseSignal = Signal() @classmethod async def create(cls, reactions: "Reactions", common: "CommonData") -> Self: raise NotImplementedError diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 88a1b08..f1bda43 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -105,13 +105,12 @@ async def onMessage(self, room: Room, message: MessageEvent): if await self.reactions.onMessage(self, event): # A reaction ran, so don't get pissy about invalid commands return - if message.user_id == self.room.userID and message.content.startswith("◈"): - return - await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if message.user_id == self.room.userID: return + await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if not message.content.startswith("!!/"): return + await self.commandRequestSignal.send_async(self, event=event) sentAt = datetime.now() response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), event)] if not len(response): @@ -123,6 +122,8 @@ async def onMessage(self, room: Room, message: MessageEvent): continue responseIDs.append(await self.room.send(line)) self.editDB[message.message_id] = (sentAt, responseIDs) + for line in response: + await self.commandResponseSignal.send_async(self, line=line) async def onEdit(self, room: Room, edit: EditEvent): event = EventInfo( @@ -134,31 +135,34 @@ async def onEdit(self, room: Room, edit: EditEvent): edit.message_id, self ) - if edit.user_id == self.room.userID and edit.content.startswith("◈"): - return - await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if edit.user_id == self.room.userID: return + await self.editSignal.send_async(self, event=event, directedAtUs=edit.content.startswith("!!/")) if not edit.content.startswith("!!/"): return + await self.commandRequestSignal.send_async(self, event=event) if edit.message_id not in self.editDB: - await self.onMessage(room, edit) + with self.messageSignal.muted(), self.commandRequestSignal.muted(): + await self.onMessage(room, edit) else: sentAt, idents = self.editDB[edit.message_id] if (datetime.now() - sentAt).seconds > (60 * 2): # margin of error - with self.messageSignal.muted(): + with self.messageSignal.muted(), self.commandRequestSignal.muted(): await self.onMessage(room, edit) - return - response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), event)] - if len(response): - response[0] = f":{edit.message_id} " + response[0] - for x in range(min(len(idents), len(response))): - await self.room.edit(idents.pop(0), response.pop(0)) - for leftover in response: - await self.room.send(leftover) - for leftover in idents: - await self.room.delete(leftover) - self.editDB.pop(edit.message_id) + else: + response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), event)] + for line in response: + await self.commandResponseSignal.send_async(line=line) + if len(response): + response[0] = f":{edit.message_id} " + response[0] + for x in range(min(len(idents), len(response))): + await self.room.edit(idents.pop(0), response.pop(0)) + for leftover in response: + await self.room.send(leftover) + for leftover in idents: + await self.room.delete(leftover) + self.editDB.pop(edit.message_id) + # we always check the DB regardless of how we responded for key, value in self.editDB.copy().items(): if (datetime.now() - value[0]).seconds > (60 * 2): self.editDB.pop(key) From b1823abb808508a464f72522d53e0f53964ba961 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 12:32:33 +0000 Subject: [PATCH 16/42] Stop GH embed spam in Discord --- vyxalbot2/github/__init__.py | 2 +- vyxalbot2/services/__init__.py | 2 +- vyxalbot2/services/discord.py | 4 ++-- vyxalbot2/services/se/__init__.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index 8a0bdbf..d82ae2b 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -28,7 +28,7 @@ async def wrapper(self: "GitHubApplication", event: GitHubEvent, services: list[ if line == PinThat: await service.pin(ids[-1]) continue - ids.append(await service.send(line)) + ids.append(await service.send(line, discordSuppressEmbeds=True)) return wrapper class GitHubApplication(Application): diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py index 0877b9a..f573503 100644 --- a/vyxalbot2/services/__init__.py +++ b/vyxalbot2/services/__init__.py @@ -31,7 +31,7 @@ async def shutdown(self): def invokeCommand(self, name: str, event: "EventInfo", *args): return self.commands.invoke(name, event, *args) - async def send(self, message: str) -> int: + async def send(self, message: str, **kwargs) -> int: raise NotImplementedError async def pin(self, message: int): diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 7b4a8eb..c94144e 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -133,8 +133,8 @@ async def shutdown(self): self.clientTask.cancel() await self.clientTask - async def send(self, message: str): - return (await self.eventChannel.send(message)).id + async def send(self, message: str, **kwargs): + return (await self.eventChannel.send(message, suppress_embeds=kwargs.get("discordSuppressEmbeds", False))).id async def pin(self, message: int): await self.eventChannel.get_partial_message(message).pin() \ No newline at end of file diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index f1bda43..f5d815f 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -77,7 +77,7 @@ async def startup(self): async def shutdown(self): await self.bot.shutdown() - async def send(self, message: str): + async def send(self, message: str, **kwargs): return await self.room.send(message) async def pin(self, message: int): From dc9e0172837f3d5b4e3d852b5eb35d1ba8b5c97e Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 12:39:48 +0000 Subject: [PATCH 17/42] big brain hours --- vyxalbot2/services/se/__init__.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index f5d815f..c312a1c 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -94,13 +94,13 @@ async def getPFP(self, user: int): async def onMessage(self, room: Room, message: MessageEvent): event = EventInfo( - message.content, - message.user_name, - await self.getPFP(message.user_id), - message.user_id, - message.room_id, - message.message_id, - self + content=message.content, + userName=message.user_name, + pfp=await self.getPFP(message.user_id), + userIdent=message.user_id, + roomIdent=message.room_id, + messageIdent=message.message_id, + service=self ) if await self.reactions.onMessage(self, event): # A reaction ran, so don't get pissy about invalid commands @@ -127,13 +127,13 @@ async def onMessage(self, room: Room, message: MessageEvent): async def onEdit(self, room: Room, edit: EditEvent): event = EventInfo( - edit.content, - edit.user_name, - await self.getPFP(edit.user_id), - edit.user_id, - edit.room_id, - edit.message_id, - self + content=edit.content, + userName=edit.user_name, + pfp=await self.getPFP(edit.user_id), + userIdent=edit.user_id, + roomIdent=edit.room_id, + messageIdent=edit.message_id, + service=self ) if edit.user_id == self.room.userID: return From 4e364ba2f90ba28b151689bb597fa97f0c6894a0 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 12:54:01 +0000 Subject: [PATCH 18/42] reaction fixes --- vyxalbot2/reactions.py | 34 +++++++++++++++++-------------- vyxalbot2/services/se/__init__.py | 10 ++++++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/vyxalbot2/reactions.py b/vyxalbot2/reactions.py index 410e2bc..c54f586 100644 --- a/vyxalbot2/reactions.py +++ b/vyxalbot2/reactions.py @@ -39,7 +39,7 @@ def __init__(self, messages: MessagesType): async def runCommand(self, service: Service, name: str, event: EventInfo, *args): async for line in service.invokeCommand(name, event, *args): - await service.send(line) + yield line async def onMessage(self, service: Service, event: EventInfo): didSomething = False @@ -51,36 +51,41 @@ async def onMessage(self, service: Service, event: EventInfo): if reMatch is not None: if event.userIdent == event.service.clientIdent and function not in OK_TO_SELF_REPLY: continue - await getattr(self, function)(service, event, reMatch) - didSomething = True - return didSomething + async for line in getattr(self, function)(service, event, reMatch): + yield line async def info(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "info", event) + async for line in self.runCommand(service, "info", event): + yield line async def cookie(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "cookie", event) + async for line in self.runCommand(service, "cookie", event): + yield line async def coffee(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "coffee", event) + async for line in self.runCommand(service, "coffee", event): + yield line async def maul(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "maul", event, reMatch.group("user")) + async for line in self.runCommand(service, "maul", event): + yield line async def sus(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "sus", event) + async for line in self.runCommand(service, "sus", event): + yield line async def blame(self, service: Service, event: EventInfo, reMatch: re.Match): - await self.runCommand(service, "blame", event) + async for line in self.runCommand(service, "blame", event): + yield line async def goodBot(self, service: Service, event: EventInfo, reMatch: re.Match): - await service.send(":3") + yield ":3" async def hello(self, service: Service, event: EventInfo, reMatch: re.Match): - await service.send(random.choice(self.messages["hello"])) + yield random.choice(self.messages["hello"]) async def goodbye(self, service: Service, event: EventInfo, reMatch: re.Match): - await service.send(random.choice(self.messages["goodbye"])) + yield random.choice(self.messages["goodbye"]) async def mojo(self, service: Service, event: EventInfo, reMatch: re.Match): emojis = [ @@ -89,5 +94,4 @@ async def mojo(self, service: Service, event: EventInfo, reMatch: re.Match): "đŸ”Ĩ" * random.randint(1, 10), ] random.shuffle(emojis) - emojis = "".join(emojis) + ("đŸ˜ŗ" * (random.randint(1, 10) == 1)) - await service.send(emojis) + yield "".join(emojis) + ("đŸ˜ŗ" * (random.randint(1, 10) == 1)) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index c312a1c..02d86c3 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -102,14 +102,18 @@ async def onMessage(self, room: Room, message: MessageEvent): messageIdent=message.message_id, service=self ) - if await self.reactions.onMessage(self, event): - # A reaction ran, so don't get pissy about invalid commands - return if message.user_id == self.room.userID: return await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if not message.content.startswith("!!/"): return + reactions = [i async for i in self.reactions.onMessage(self, event)] + if len(reactions): + await self.commandRequestSignal.send_async(self, event=event) + for line in reactions: + await self.send(line) + await self.commandResponseSignal.send_async(self, line=line) + return await self.commandRequestSignal.send_async(self, event=event) sentAt = datetime.now() response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), event)] From 229267f9bc47fd0e8bad7311e9ce81a39ed80ec8 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 12:57:45 +0000 Subject: [PATCH 19/42] das webhooken --- vyxalbot2/bridge.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index 42dc686..348bdee 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -40,12 +40,16 @@ async def onCommandResponse(self, sender, line: str): async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): if directedAtUs: return - assert self.webhook is not None + if self.webhook is None: + self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") + return await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): assert self.discord.client.user is not None - assert self.webhook is not None + if self.webhook is None: + self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") + return if event.roomIdent != self.channel.id: return if event.userIdent == self.discord.client.user.id: From 130cd318d4c2017ea9def217ee573c8dc5b9120a Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:00:56 +0000 Subject: [PATCH 20/42] das reactionen --- vyxalbot2/services/discord.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index c94144e..f92fd90 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -119,7 +119,7 @@ async def startup(self): self.logger.info(f"Discord connection established! We are {self.client.user}.") async def on_message(self, message: Message): - await self.messageSignal.send_async(self, event=EventInfo( + event = EventInfo( content=message.content, userName=message.author.display_name, pfp=message.author.display_avatar.url, @@ -127,7 +127,13 @@ async def on_message(self, message: Message): userIdent=message.author.id, messageIdent=message.id, service=self - )) + ) + await self.messageSignal.send_async(self, event=event) + channel = self.client.get_channel(message.channel.id) + if not isinstance(channel, TextChannel): + return + async for line in self.reactions.onMessage(self, event): + await channel.send(line) async def shutdown(self): self.clientTask.cancel() From d73ab304f542b579c41411b768abc00c4e94b63c Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:04:19 +0000 Subject: [PATCH 21/42] reorder ifs so reactions work --- vyxalbot2/services/se/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 02d86c3..fdbbdf7 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -105,8 +105,6 @@ async def onMessage(self, room: Room, message: MessageEvent): if message.user_id == self.room.userID: return await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) - if not message.content.startswith("!!/"): - return reactions = [i async for i in self.reactions.onMessage(self, event)] if len(reactions): await self.commandRequestSignal.send_async(self, event=event) @@ -114,6 +112,8 @@ async def onMessage(self, room: Room, message: MessageEvent): await self.send(line) await self.commandResponseSignal.send_async(self, line=line) return + if not message.content.startswith("!!/"): + return await self.commandRequestSignal.send_async(self, event=event) sentAt = datetime.now() response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), event)] From 4e0c8a127f10d4d6eb1e65cde2a221c8e6b76791 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:06:50 +0000 Subject: [PATCH 22/42] augh --- vyxalbot2/services/discord.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index f92fd90..e91e63c 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -128,12 +128,15 @@ async def on_message(self, message: Message): messageIdent=message.id, service=self ) - await self.messageSignal.send_async(self, event=event) channel = self.client.get_channel(message.channel.id) - if not isinstance(channel, TextChannel): - return - async for line in self.reactions.onMessage(self, event): - await channel.send(line) + reactions = [i async for i in self.reactions.onMessage(self, event)] + if len(reactions): + if not isinstance(channel, TextChannel): + return + for line in reactions: + await channel.send(line) + await self.messageSignal.send_async(self, event=event) + async def shutdown(self): self.clientTask.cancel() From cb8cc0f8ecbd8049a77fdbfb1a6a5c7c30e9ddd4 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:08:52 +0000 Subject: [PATCH 23/42] again? --- vyxalbot2/services/discord.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index e91e63c..1c6c6ab 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -135,6 +135,7 @@ async def on_message(self, message: Message): return for line in reactions: await channel.send(line) + return await self.messageSignal.send_async(self, event=event) From 1a9f9806d200fe08434e11fcbb86a063574b76ba Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:11:59 +0000 Subject: [PATCH 24/42] Add more guardrails --- vyxalbot2/services/discord.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 1c6c6ab..55bb1bc 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -128,6 +128,11 @@ async def on_message(self, message: Message): messageIdent=message.id, service=self ) + assert self.client.user is not None + if message.author.id == self.client.user.id: + return + if message.channel.id == self.common.privateConfig["discord"]["bridgeChannel"]: + return channel = self.client.get_channel(message.channel.id) reactions = [i async for i in self.reactions.onMessage(self, event)] if len(reactions): From ccdd2389f13c2170f64632e92d45d584f58f123f Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:16:39 +0000 Subject: [PATCH 25/42] fix order of calls --- vyxalbot2/services/se/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index fdbbdf7..fec11f0 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -104,7 +104,6 @@ async def onMessage(self, room: Room, message: MessageEvent): ) if message.user_id == self.room.userID: return - await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) reactions = [i async for i in self.reactions.onMessage(self, event)] if len(reactions): await self.commandRequestSignal.send_async(self, event=event) @@ -112,6 +111,7 @@ async def onMessage(self, room: Room, message: MessageEvent): await self.send(line) await self.commandResponseSignal.send_async(self, line=line) return + await self.messageSignal.send_async(self, event=event, directedAtUs=message.content.startswith("!!/")) if not message.content.startswith("!!/"): return await self.commandRequestSignal.send_async(self, event=event) From 0d24358a42a011aee15b16bb8fb70624027bbf9d Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:18:36 +0000 Subject: [PATCH 26/42] maybe this? --- vyxalbot2/services/discord.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 55bb1bc..ee93fa0 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -131,8 +131,6 @@ async def on_message(self, message: Message): assert self.client.user is not None if message.author.id == self.client.user.id: return - if message.channel.id == self.common.privateConfig["discord"]["bridgeChannel"]: - return channel = self.client.get_channel(message.channel.id) reactions = [i async for i in self.reactions.onMessage(self, event)] if len(reactions): From 47ad12e922db0cc471852ae35b900d42440ba13d Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:23:28 +0000 Subject: [PATCH 27/42] markdownify --- vyxalbot2/bridge.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index 348bdee..e8e4d34 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -1,6 +1,9 @@ from logging import getLogger + from aiohttp import ClientSession -from discord import Client, Guild, TextChannel +from discord import Guild, TextChannel +from markdownify import markdownify + from vyxalbot2.services import Service from vyxalbot2.services.discord import DiscordService from vyxalbot2.types import CommonData, EventInfo @@ -43,7 +46,7 @@ async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): if self.webhook is None: self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") return - await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) + await self.webhook.send(markdownify(event.content), username=event.userName, avatar_url=event.pfp) async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): assert self.discord.client.user is not None From d65bdcffe3e11c474c4e85effd5030525e61e2cf Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:39:01 +0000 Subject: [PATCH 28/42] pretty discord formatting --- vyxalbot2/services/discord.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index ee93fa0..3328102 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -3,6 +3,7 @@ import logging import inspect +import re from discord import Client, CustomActivity, Game, Intents, Interaction, Message, Object, TextChannel from discord.app_commands import CommandTree, Command as DiscordCommand, Group @@ -128,6 +129,10 @@ async def on_message(self, message: Message): messageIdent=message.id, service=self ) + event.content = re.sub(r"<:(\w+):(\d+)>", lambda m: m.group(1), event.content) + for embed in message.embeds: + if embed.image is not None and embed.image.url is not None: + event.content += " " + embed.image.url assert self.client.user is not None if message.author.id == self.client.user.id: return From adc2e199dfa05d1581a5146a9c90a903ea5f7a6b Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:54:05 +0000 Subject: [PATCH 29/42] markdownify 2 --- poetry.lock | 2 +- pyproject.toml | 1 + vyxalbot2/bridge.py | 3 +-- vyxalbot2/services/se/__init__.py | 44 ++++++++++++++----------------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/poetry.lock b/poetry.lock index 982ea06..c076113 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1329,4 +1329,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f4051654b3c3b2d9aec30e6f38032ef904ceeb3093524f0bae351233a607798a" +content-hash = "d883922e616524da85bea31e7c8b3256ef92506fb72312b5a72ea7ecf8ec02b7" diff --git a/pyproject.toml b/pyproject.toml index c09308a..67d541e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ odmantic = "^0.9.2" blinker = "^1.7.0" pydantic = "<2.0" discord-py = "^2.3.2" +bs4 = "^0.0.1" [tool.poetry.group.dev.dependencies] black = {version = "^23.9.1", allow-prereleases = true} diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py index e8e4d34..008c1b0 100644 --- a/vyxalbot2/bridge.py +++ b/vyxalbot2/bridge.py @@ -2,7 +2,6 @@ from aiohttp import ClientSession from discord import Guild, TextChannel -from markdownify import markdownify from vyxalbot2.services import Service from vyxalbot2.services.discord import DiscordService @@ -46,7 +45,7 @@ async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): if self.webhook is None: self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") return - await self.webhook.send(markdownify(event.content), username=event.userName, avatar_url=event.pfp) + await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): assert self.discord.client.user is not None diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index fec11f0..8e2ab28 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -1,38 +1,23 @@ -from asyncio import wait_for -import inspect -from time import time -from typing import Callable from datetime import datetime -from string import ascii_letters +from typing import cast +from urllib.parse import urlparse, urlunparse -import re import random -import codecs -import base64 -import json -import subprocess -from discord import User - -from gidgethub import BadRequest, HTTPException as GitHubHTTPException, ValidationError -from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI +import logging + from aiohttp import ClientSession +from bs4 import BeautifulSoup, Tag from sechat import Bot, EventType -from tinydb.table import Document from sechat.room import Room from sechat.events import MessageEvent, EditEvent -from uwuivy import uwuipy +from markdownify import MarkdownConverter -import yaml -import logging -from vyxalbot2.commands.common import CommonCommands from vyxalbot2.commands.se import SECommands from vyxalbot2.reactions import Reactions -from vyxalbot2.github import GitHubApplication from vyxalbot2.services import PinThat, Service from vyxalbot2.services.se.parser import CommandParser, ParseError -from vyxalbot2.types import CommonData, EventInfo, PrivateConfigType, PublicConfigType, MessagesType -from vyxalbot2.userdb import UserDB +from vyxalbot2.types import CommonData, EventInfo from vyxalbot2.util import resolveChatPFP class SEService(Service): @@ -55,6 +40,7 @@ def __init__(self, bot: Bot, room: Room, reactions: Reactions, common: CommonDat self.room = room self.common = common self.reactions = reactions + self.converter = MarkdownConverter(autolinks=False) self.pfpCache: dict[int, str] = {} @@ -92,9 +78,19 @@ async def getPFP(self, user: int): self.pfpCache[user] = resolveChatPFP((await response.json())["email_hash"]) return self.pfpCache[user] + def preprocessMessage(self, message: str): + soup = BeautifulSoup(message) + for tag in soup.find_all("a"): + if not isinstance(tag, Tag): + continue + url = urlparse(tag.attrs["href"]) + if not url.netloc: + tag.attrs["href"] = urlunparse(("https", "chat.stackexchange.com", url.path, url.params, url.query, url.fragment)) + return cast(str, self.converter.convert_soup(soup)) + async def onMessage(self, room: Room, message: MessageEvent): event = EventInfo( - content=message.content, + content=self.preprocessMessage(message.content), userName=message.user_name, pfp=await self.getPFP(message.user_id), userIdent=message.user_id, @@ -154,7 +150,7 @@ async def onEdit(self, room: Room, edit: EditEvent): with self.messageSignal.muted(), self.commandRequestSignal.muted(): await self.onMessage(room, edit) else: - response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), event)] + response = [i async for i in self.processMessage(self.preprocessMessage(edit.content.removeprefix("!!/")), event)] for line in response: await self.commandResponseSignal.send_async(line=line) if len(response): From 9531caf8ad8af0b5993e2eb58e770ebb653c0171 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 14:02:57 +0000 Subject: [PATCH 30/42] image magick --- vyxalbot2/services/se/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 8e2ab28..dbf6382 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -86,6 +86,12 @@ def preprocessMessage(self, message: str): url = urlparse(tag.attrs["href"]) if not url.netloc: tag.attrs["href"] = urlunparse(("https", "chat.stackexchange.com", url.path, url.params, url.query, url.fragment)) + elif not url.scheme: + tag.attrs["href"] = urlunparse(("https", url.netloc, url.path, url.params, url.query, url.fragment)) + for tag in soup.find_all("img"): + if not isinstance(tag, Tag): + continue + tag.replace_with(f"""{tag.attrs['src']}""") return cast(str, self.converter.convert_soup(soup)) async def onMessage(self, room: Room, message: MessageEvent): From 382f2c69d4e6fba215358b65d18f89fd611e6321 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Sat, 4 Nov 2023 14:03:48 +0000 Subject: [PATCH 31/42] ygygygyg --- vyxalbot2/services/se/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index dbf6382..f426e71 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -91,7 +91,7 @@ def preprocessMessage(self, message: str): for tag in soup.find_all("img"): if not isinstance(tag, Tag): continue - tag.replace_with(f"""{tag.attrs['src']}""") + tag.replace_with(tag.attrs["src"]) return cast(str, self.converter.convert_soup(soup)) async def onMessage(self, room: Room, message: MessageEvent): From 1cd8d40006e2499e3e4cdc90eeca5a91376c5e4b Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:35:38 +0000 Subject: [PATCH 32/42] Nuke the bridge once and for all --- vyxalbot2/__init__.py | 5 +--- vyxalbot2/bridge.py | 61 ------------------------------------------- 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 vyxalbot2/bridge.py diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index 91e3ad3..bff0e72 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -32,10 +32,9 @@ from gidgethub.apps import get_installation_access_token, get_jwt from cachetools import LRUCache from dateutil.parser import parse as parseDatetime -from uwuivy import uwuipy +from uwuipy import uwuipy from discord.utils import setup_logging from motor.motor_asyncio import AsyncIOMotorClient -from vyxalbot2.bridge import DiscordBridge from vyxalbot2.commands.common import CommonCommands from vyxalbot2.github import GitHubApplication @@ -85,8 +84,6 @@ async def run(self): ) self.se = await SEService.create(reactions, common) self.discord = await DiscordService.create(reactions, common) - bridge = DiscordBridge(self.se, self.discord, self.discord.client.guilds[0], common) - await bridge.start() ghApp.services.append(self.se) ghApp.services.append(self.discord) diff --git a/vyxalbot2/bridge.py b/vyxalbot2/bridge.py deleted file mode 100644 index 008c1b0..0000000 --- a/vyxalbot2/bridge.py +++ /dev/null @@ -1,61 +0,0 @@ -from logging import getLogger - -from aiohttp import ClientSession -from discord import Guild, TextChannel - -from vyxalbot2.services import Service -from vyxalbot2.services.discord import DiscordService -from vyxalbot2.types import CommonData, EventInfo -from vyxalbot2.userdb import User - - -class DiscordBridge: - def __init__(self, source: Service, discord: DiscordService, guild: Guild, common: CommonData): - self.logger = getLogger("DiscordBridge") - self.source = source - self.discord = discord - self.guild = guild - self.common = common - - self.webhook = None - - channel = self.guild.get_channel(common.privateConfig["discord"]["bridgeChannel"]) - assert isinstance(channel, TextChannel) - self.channel = channel - source.messageSignal.connect(self.onMessage, source, False) - source.commandRequestSignal.connect(self.onMessage, source, False) - source.commandResponseSignal.connect(self.onCommandResponse, source, False) - discord.messageSignal.connect(self.onDiscordMessage, discord, False) - - async def fetchPFP(self, pfp: str): - async with ClientSession() as session: - async with session.get(pfp) as response: - return await response.content.read() - - async def start(self): - self.webhook = (await self.channel.webhooks())[0] - self.logger.info("Ready!") - - async def onCommandResponse(self, sender, line: str): - await self.channel.send(line) - - async def onMessage(self, sender, event: EventInfo, directedAtUs: bool = False): - if directedAtUs: - return - if self.webhook is None: - self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") - return - await self.webhook.send(event.content, username=event.userName, avatar_url=event.pfp) - - async def onDiscordMessage(self, sender, event: EventInfo, directedAtUs: bool = False): - assert self.discord.client.user is not None - if self.webhook is None: - self.logger.warning(f"Dropping message {event.messageIdent}, webhook isn't ready yet") - return - if event.roomIdent != self.channel.id: - return - if event.userIdent == self.discord.client.user.id: - return - if event.userIdent == self.webhook.id: - return - await self.source.send(f"◈ [{event.userName}] " + event.content) \ No newline at end of file From 65d4eeddaf6d6142d88372132103675f09188271 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:51:48 +0000 Subject: [PATCH 33/42] fixes --- poetry.lock | 20 ++++++++------------ pyproject.toml | 2 +- vyxalbot2/commands/common.py | 4 ++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index c076113..880b4fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1147,19 +1147,15 @@ files = [ ] [[package]] -name = "uwuivy" -version = "0.1.0" +name = "uwuipy" +version = "0.1.7" description = "Allows the easy implementation of uwuifying words for applications like Discord bots and websites" optional = false -python-versions = "^3.10" -files = [] -develop = false - -[package.source] -type = "git" -url = "https://github.com/Vyxal/uwuivy.git" -reference = "v0.1.0" -resolved_reference = "0284e97d92043e775aba88ae442f4e275fc52e73" +python-versions = ">=3.10,<4.0" +files = [ + {file = "uwuipy-0.1.7-py3-none-any.whl", hash = "sha256:db26b361e4466c85ae06fe1a663ce77167e16993c9e848511cd49f74107ff1e8"}, + {file = "uwuipy-0.1.7.tar.gz", hash = "sha256:94915fbb5f897ab9532d5d39e7e4b726fd8da8c3352efcfb1c1df98a5cf9ec04"}, +] [[package]] name = "websockets" @@ -1329,4 +1325,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "d883922e616524da85bea31e7c8b3256ef92506fb72312b5a72ea7ecf8ec02b7" +content-hash = "2989681acadae96789f699dbadf7345cc7ccd63cd9897ba01b40e406aa264bab" diff --git a/pyproject.toml b/pyproject.toml index 67d541e..d829908 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,13 +15,13 @@ tinydb = "^4.7.1" pyjwt = "^2.7.0" python-dateutil = "^2.8.2" websockets = "^10.4" -uwuivy = { git = "https://github.com/Vyxal/uwuivy.git", branch = "v0.1.0" } pyyaml = "^6.0" odmantic = "^0.9.2" blinker = "^1.7.0" pydantic = "<2.0" discord-py = "^2.3.2" bs4 = "^0.0.1" +uwuipy = "^0.1.7" [tool.poetry.group.dev.dependencies] black = {version = "^23.9.1", allow-prereleases = true} diff --git a/vyxalbot2/commands/common.py b/vyxalbot2/commands/common.py index c48b486..da6c01c 100644 --- a/vyxalbot2/commands/common.py +++ b/vyxalbot2/commands/common.py @@ -13,7 +13,7 @@ from vyxalbot2.commands import CommandSupplier from vyxalbot2.types import CommonData, EventInfo from vyxalbot2.util import RAPTOR -from uwuivy import uwuipy +from uwuipy import uwuipy class StatusMood(Enum): MESSAGE = "message" @@ -140,4 +140,4 @@ async def commitCommand(self, event: EventInfo): if result.returncode != 0: yield "Failed to get commit info!" else: - yield f"Commit: {result.stdout.decode('utf-8').strip()}" \ No newline at end of file + yield f"Commit: {result.stdout.decode('utf-8').strip()}" From 24e8855e0905d8ad88944c1a59c54432161631f9 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:52:05 +0000 Subject: [PATCH 34/42] mojon't --- vyxalbot2/__init__.py | 2 +- vyxalbot2/reactions.py | 5 ++++- vyxalbot2/services/se/__init__.py | 2 ++ vyxalbot2/types.py | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index bff0e72..c54a7f2 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -70,7 +70,7 @@ async def run(self): userDB = UserDB(AsyncIOMotorClient(self.privateConfig["mongoUrl"]), self.privateConfig["database"]) ghApp = GitHubApplication(self.publicConfig, self.privkey, self.privateConfig["appID"], self.privateConfig["account"], self.privateConfig["webhookSecret"]) - reactions = Reactions(self.messages) + reactions = Reactions(self.messages, self.privateConfig["chat"]["ignore"]) common = CommonData( self.statuses, diff --git a/vyxalbot2/reactions.py b/vyxalbot2/reactions.py index c54f586..f8f1bc9 100644 --- a/vyxalbot2/reactions.py +++ b/vyxalbot2/reactions.py @@ -34,8 +34,9 @@ ) class Reactions: - def __init__(self, messages: MessagesType): + def __init__(self, messages: MessagesType, ignore: list[int]): self.messages = messages + self.ignore = ignore async def runCommand(self, service: Service, name: str, event: EventInfo, *args): async for line in service.invokeCommand(name, event, *args): @@ -51,6 +52,8 @@ async def onMessage(self, service: Service, event: EventInfo): if reMatch is not None: if event.userIdent == event.service.clientIdent and function not in OK_TO_SELF_REPLY: continue + if event.userIdent in self.ignore: + continue async for line in getattr(self, function)(service, event, reMatch): yield line diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index f426e71..2806ccd 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -106,6 +106,8 @@ async def onMessage(self, room: Room, message: MessageEvent): ) if message.user_id == self.room.userID: return + if message.user_id in self.common.privateConfig["chat"]["ignore"]: + return reactions = [i async for i in self.reactions.onMessage(self, event)] if len(reactions): await self.commandRequestSignal.send_async(self, event=event) diff --git a/vyxalbot2/types.py b/vyxalbot2/types.py index 3b49654..87a2280 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -27,6 +27,7 @@ class ChatConfigType(TypedDict): room: int email: str password: str + ignore: list[int] class DiscordConfigType(TypedDict): token: str From 2e5650ad70e178e35157f4651ada9cde7cfc29e4 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:05:49 +0000 Subject: [PATCH 35/42] ignore webhooks --- vyxalbot2/services/discord.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 3328102..303b21e 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -120,6 +120,8 @@ async def startup(self): self.logger.info(f"Discord connection established! We are {self.client.user}.") async def on_message(self, message: Message): + if message.author.discriminator == "0000": + return event = EventInfo( content=message.content, userName=message.author.display_name, From fd7bd9e8a398e6e377d359236fb6bd81659d11ac Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:11:42 +0000 Subject: [PATCH 36/42] Make Bridget ignore GH notifications --- vyxalbot2/github/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index d82ae2b..953dd11 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -28,7 +28,8 @@ async def wrapper(self: "GitHubApplication", event: GitHubEvent, services: list[ if line == PinThat: await service.pin(ids[-1]) continue - ids.append(await service.send(line, discordSuppressEmbeds=True)) + # ZWJ so Bridget ignores it + ids.append(await service.send("\u200d" + line, discordSuppressEmbeds=True)) return wrapper class GitHubApplication(Application): From 74c29623779420fba4dcc01babea70e514bf9f3c Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:23:35 +0000 Subject: [PATCH 37/42] Apply commit spam changes and import cleanup --- poetry.lock | 531 ++++++++++++++--------------------- vyxalbot2/github/__init__.py | 49 ++-- 2 files changed, 251 insertions(+), 329 deletions(-) diff --git a/poetry.lock b/poetry.lock index 880b4fa..0578f5f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,111 +2,99 @@ [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.1" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, + {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, + {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, + {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, + {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, + {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, + {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, + {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, + {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, + {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, + {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, + {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" @@ -182,29 +170,29 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.10.1" +version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, - {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, - {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, - {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, - {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, - {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, - {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, - {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, - {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, - {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, - {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, - {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] @@ -321,105 +309,6 @@ files = [ [package.dependencies] pycparser = "*" -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - [[package]] name = "click" version = "8.1.7" @@ -447,34 +336,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -624,13 +513,13 @@ tornado = ["tornado"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -1078,7 +967,7 @@ websockets = "^10.4" type = "git" url = "https://github.com/GingerIndustries/sechat.git" reference = "v2" -resolved_reference = "d9e6fe4efa23621cd88403ba83a81218213a7747" +resolved_reference = "5bf2dc2d820beaf6a56d1009a6422b6e1229a376" [[package]] name = "six" @@ -1237,85 +1126,101 @@ files = [ [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.3" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32435d134414e01d937cd9d6cc56e8413a8d4741dea36af5840c7750f04d16ab"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a5211de242754b5e612557bca701f39f8b1a9408dff73c6db623f22d20f470e"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:525cd69eff44833b01f8ef39aa33a9cc53a99ff7f9d76a6ef6a9fb758f54d0ff"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc94441bcf9cb8c59f51f23193316afefbf3ff858460cb47b5758bf66a14d130"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e36021db54b8a0475805acc1d6c4bca5d9f52c3825ad29ae2d398a9d530ddb88"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0f17d1df951336a02afc8270c03c0c6e60d1f9996fcbd43a4ce6be81de0bd9d"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f3faeb8100a43adf3e7925d556801d14b5816a0ac9e75e22948e787feec642"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aed37db837ecb5962469fad448aaae0f0ee94ffce2062cf2eb9aed13328b5196"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:721ee3fc292f0d069a04016ef2c3a25595d48c5b8ddc6029be46f6158d129c92"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b8bc5b87a65a4e64bc83385c05145ea901b613d0d3a434d434b55511b6ab0067"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:dd952b9c64f3b21aedd09b8fe958e4931864dba69926d8a90c90d36ac4e28c9a"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:c405d482c320a88ab53dcbd98d6d6f32ada074f2d965d6e9bf2d823158fa97de"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9df9a0d4c5624790a0dea2e02e3b1b3c69aed14bcb8650e19606d9df3719e87d"}, + {file = "yarl-1.9.3-cp310-cp310-win32.whl", hash = "sha256:d34c4f80956227f2686ddea5b3585e109c2733e2d4ef12eb1b8b4e84f09a2ab6"}, + {file = "yarl-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:cf7a4e8de7f1092829caef66fd90eaf3710bc5efd322a816d5677b7664893c93"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d61a0ca95503867d4d627517bcfdc28a8468c3f1b0b06c626f30dd759d3999fd"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73cc83f918b69110813a7d95024266072d987b903a623ecae673d1e71579d566"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d81657b23e0edb84b37167e98aefb04ae16cbc5352770057893bd222cdc6e45f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a1a8443091c7fbc17b84a0d9f38de34b8423b459fb853e6c8cdfab0eacf613"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe34befb8c765b8ce562f0200afda3578f8abb159c76de3ab354c80b72244c41"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c757f64afe53a422e45e3e399e1e3cf82b7a2f244796ce80d8ca53e16a49b9f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a57b41a0920b9a220125081c1e191b88a4cdec13bf9d0649e382a822705c65"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632c7aeb99df718765adf58eacb9acb9cbc555e075da849c1378ef4d18bf536a"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b0b8c06afcf2bac5a50b37f64efbde978b7f9dc88842ce9729c020dc71fae4ce"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1d93461e2cf76c4796355494f15ffcb50a3c198cc2d601ad8d6a96219a10c363"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4003f380dac50328c85e85416aca6985536812c082387255c35292cb4b41707e"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d6d74a97e898c1c2df80339aa423234ad9ea2052f66366cef1e80448798c13d"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b61e64b06c3640feab73fa4ff9cb64bd8182de52e5dc13038e01cfe674ebc321"}, + {file = "yarl-1.9.3-cp311-cp311-win32.whl", hash = "sha256:29beac86f33d6c7ab1d79bd0213aa7aed2d2f555386856bb3056d5fdd9dab279"}, + {file = "yarl-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7271d6bd8838c49ba8ae647fc06469137e1c161a7ef97d778b72904d9b68696"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dd318e6b75ca80bff0b22b302f83a8ee41c62b8ac662ddb49f67ec97e799885d"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4b1efb11a8acd13246ffb0bee888dd0e8eb057f8bf30112e3e21e421eb82d4a"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f034386e5550b5dc8ded90b5e2ff7db21f0f5c7de37b6efc5dac046eb19c10"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd49a908cb6d387fc26acee8b7d9fcc9bbf8e1aca890c0b2fdfd706057546080"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa4643635f26052401750bd54db911b6342eb1a9ac3e74f0f8b58a25d61dfe41"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e741bd48e6a417bdfbae02e088f60018286d6c141639359fb8df017a3b69415a"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c86d0d0919952d05df880a1889a4f0aeb6868e98961c090e335671dea5c0361"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d5434b34100b504aabae75f0622ebb85defffe7b64ad8f52b8b30ec6ef6e4b9"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79e1df60f7c2b148722fb6cafebffe1acd95fd8b5fd77795f56247edaf326752"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:44e91a669c43f03964f672c5a234ae0d7a4d49c9b85d1baa93dec28afa28ffbd"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3cfa4dbe17b2e6fca1414e9c3bcc216f6930cb18ea7646e7d0d52792ac196808"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:88d2c3cc4b2f46d1ba73d81c51ec0e486f59cc51165ea4f789677f91a303a9a7"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cccdc02e46d2bd7cb5f38f8cc3d9db0d24951abd082b2f242c9e9f59c0ab2af3"}, + {file = "yarl-1.9.3-cp312-cp312-win32.whl", hash = "sha256:96758e56dceb8a70f8a5cff1e452daaeff07d1cc9f11e9b0c951330f0a2396a7"}, + {file = "yarl-1.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4472fe53ebf541113e533971bd8c32728debc4c6d8cc177f2bff31d011ec17e"}, + {file = "yarl-1.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:126638ab961633f0940a06e1c9d59919003ef212a15869708dcb7305f91a6732"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c99ddaddb2fbe04953b84d1651149a0d85214780e4d0ee824e610ab549d98d92"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dab30b21bd6fb17c3f4684868c7e6a9e8468078db00f599fb1c14e324b10fca"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:828235a2a169160ee73a2fcfb8a000709edf09d7511fccf203465c3d5acc59e4"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc391e3941045fd0987c77484b2799adffd08e4b6735c4ee5f054366a2e1551d"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51382c72dd5377861b573bd55dcf680df54cea84147c8648b15ac507fbef984d"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:28a108cb92ce6cf867690a962372996ca332d8cda0210c5ad487fe996e76b8bb"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8f18a7832ff85dfcd77871fe677b169b1bc60c021978c90c3bb14f727596e0ae"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:7eaf13af79950142ab2bbb8362f8d8d935be9aaf8df1df89c86c3231e4ff238a"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:66a6dbf6ca7d2db03cc61cafe1ee6be838ce0fbc97781881a22a58a7c5efef42"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a0a4f3aaa18580038cfa52a7183c8ffbbe7d727fe581300817efc1e96d1b0e9"}, + {file = "yarl-1.9.3-cp37-cp37m-win32.whl", hash = "sha256:946db4511b2d815979d733ac6a961f47e20a29c297be0d55b6d4b77ee4b298f6"}, + {file = "yarl-1.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2dad8166d41ebd1f76ce107cf6a31e39801aee3844a54a90af23278b072f1ccf"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bb72d2a94481e7dc7a0c522673db288f31849800d6ce2435317376a345728225"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a172c3d5447b7da1680a1a2d6ecdf6f87a319d21d52729f45ec938a7006d5d8"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2dc72e891672343b99db6d497024bf8b985537ad6c393359dc5227ef653b2f17"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8d51817cf4b8d545963ec65ff06c1b92e5765aa98831678d0e2240b6e9fd281"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53ec65f7eee8655bebb1f6f1607760d123c3c115a324b443df4f916383482a67"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cfd77e8e5cafba3fb584e0f4b935a59216f352b73d4987be3af51f43a862c403"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e73db54c967eb75037c178a54445c5a4e7461b5203b27c45ef656a81787c0c1b"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09c19e5f4404574fcfb736efecf75844ffe8610606f3fccc35a1515b8b6712c4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6280353940f7e5e2efaaabd686193e61351e966cc02f401761c4d87f48c89ea4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c25ec06e4241e162f5d1f57c370f4078797ade95c9208bd0c60f484834f09c96"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7217234b10c64b52cc39a8d82550342ae2e45be34f5bff02b890b8c452eb48d7"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4ce77d289f8d40905c054b63f29851ecbfd026ef4ba5c371a158cfe6f623663e"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f74b015c99a5eac5ae589de27a1201418a5d9d460e89ccb3366015c6153e60a"}, + {file = "yarl-1.9.3-cp38-cp38-win32.whl", hash = "sha256:8a2538806be846ea25e90c28786136932ec385c7ff3bc1148e45125984783dc6"}, + {file = "yarl-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:6465d36381af057d0fab4e0f24ef0e80ba61f03fe43e6eeccbe0056e74aadc70"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2f3c8822bc8fb4a347a192dd6a28a25d7f0ea3262e826d7d4ef9cc99cd06d07e"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7831566595fe88ba17ea80e4b61c0eb599f84c85acaa14bf04dd90319a45b90"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff34cb09a332832d1cf38acd0f604c068665192c6107a439a92abfd8acf90fe2"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe8080b4f25dfc44a86bedd14bc4f9d469dfc6456e6f3c5d9077e81a5fedfba7"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8535e111a064f3bdd94c0ed443105934d6f005adad68dd13ce50a488a0ad1bf3"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d155a092bf0ebf4a9f6f3b7a650dc5d9a5bbb585ef83a52ed36ba46f55cc39d"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:778df71c8d0c8c9f1b378624b26431ca80041660d7be7c3f724b2c7a6e65d0d6"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f9cafaf031c34d95c1528c16b2fa07b710e6056b3c4e2e34e9317072da5d1a"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ca6b66f69e30f6e180d52f14d91ac854b8119553b524e0e28d5291a724f0f423"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0e7e83f31e23c5d00ff618045ddc5e916f9e613d33c5a5823bc0b0a0feb522f"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:af52725c7c39b0ee655befbbab5b9a1b209e01bb39128dce0db226a10014aacc"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0ab5baaea8450f4a3e241ef17e3d129b2143e38a685036b075976b9c415ea3eb"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d350388ba1129bc867c6af1cd17da2b197dff0d2801036d2d7d83c2d771a682"}, + {file = "yarl-1.9.3-cp39-cp39-win32.whl", hash = "sha256:e2a16ef5fa2382af83bef4a18c1b3bcb4284c4732906aa69422cf09df9c59f1f"}, + {file = "yarl-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:d92d897cb4b4bf915fbeb5e604c7911021a8456f0964f3b8ebbe7f9188b9eabb"}, + {file = "yarl-1.9.3-py3-none-any.whl", hash = "sha256:271d63396460b6607b588555ea27a1a02b717ca2e3f2cf53bdde4013d7790929"}, + {file = "yarl-1.9.3.tar.gz", hash = "sha256:4a14907b597ec55740f63e52d7fee0e9ee09d5b9d57a4f399a7423268e457b57"}, ] [package.dependencies] diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index 953dd11..f4a9fa0 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -1,22 +1,23 @@ -import re +from typing import Optional +from collections import Counter, defaultdict from time import time -from typing import Optional +import re + from aiohttp import ClientSession -from aiohttp.web import Application, Request, Response, run_app +from aiohttp.web import Application, Request, Response from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI from gidgethub.routing import Router from gidgethub.sansio import Event as GitHubEvent -from gidgethub.apps import get_installation_access_token, get_jwt +from gidgethub.apps import get_installation_access_token from dateutil.parser import parse as parseDatetime from cachetools import LRUCache -import jwt -from sechat import Room from vyxalbot2.services import PinThat, Service -from vyxalbot2.types import AppToken, PublicConfigType +import jwt -from .formatters import formatIssue, formatRef, formatRepo, formatUser, msgify +from vyxalbot2.types import AppToken, PublicConfigType +from vyxalbot2.github.formatters import formatIssue, formatRef, formatRepo, formatUser, msgify from vyxalbot2.util import GITHUB_MERGE_QUEUE def wrap(fun): @@ -167,14 +168,30 @@ async def onPushAction(self, event: GitHubEvent): ): return branch = "/".join(event.data["ref"].split("/")[2:]) - for commit in event.data["commits"]: - if not commit["distinct"]: - continue - if event.data["pusher"]["name"] == event.data["sender"]["login"]: - user = formatUser(event.data["sender"]) - else: - user = event.data["pusher"]["name"] - yield f"{user} {'force-pushed' if event.data['forced'] else 'pushed'} a [commit]({commit['url']}) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commit['message'].splitlines()[0]}" + verb = "force-push" if event.data["forced"] else "push" + if len(event.data["commits"]) <= 5: + for commit in event.data["commits"]: + if not commit["distinct"]: + continue + if event.data["pusher"]["name"] == event.data["sender"]["login"]: + user = formatUser(event.data["sender"]) + else: + user = event.data["pusher"]["name"] + yield f"{user} {verb}ed a [commit]({commit['url']}) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commit['message'].splitlines()[0]}" + else: + counter = Counter() + userCommits = defaultdict(lambda: []) + for commit in event.data["commits"]: + if not commit["distinct"]: + continue + name = event.data["pusher"]["name"] + counter[name] += 1 + userCommits[name].append(commit) + for user, count in counter.items(): + commits = userCommits[user] + if user == event.data["sender"]["login"]: + user = formatUser(event.data["sender"]) + yield f"{user} {verb}ed {count} commits ([s]({commits[0]['url']}) [e]({commits[-1]['url']})) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commits[-1]['message'].splitlines()[0]}" @wrap async def onIssueAction(self, event: GitHubEvent): From 001d505bc5001cb65f6ca23c038e6f65bccd7eb9 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:29:24 +0000 Subject: [PATCH 38/42] Source cleanup --- poetry.lock | 13 +-------- pyproject.toml | 1 - vyxalbot2/__init__.py | 37 ++++-------------------- vyxalbot2/commands/__init__.py | 1 - vyxalbot2/commands/common.py | 4 +-- vyxalbot2/commands/se.py | 3 +- vyxalbot2/github/__init__.py | 7 ++--- vyxalbot2/reactions.py | 8 ++---- vyxalbot2/services/__init__.py | 2 +- vyxalbot2/services/discord.py | 5 ++-- vyxalbot2/services/se/__init__.py | 3 +- vyxalbot2/services/se/parser.py | 5 ---- vyxalbot2/types.py | 3 +- vyxalbot2/userdb.py | 48 ++----------------------------- vyxalbot2/util.py | 3 +- 15 files changed, 24 insertions(+), 119 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0578f5f..871e880 100644 --- a/poetry.lock +++ b/poetry.lock @@ -991,17 +991,6 @@ files = [ {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] -[[package]] -name = "tinydb" -version = "4.8.0" -description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "tinydb-4.8.0-py3-none-any.whl", hash = "sha256:30c06d12383d7c332e404ca6a6103fb2b32cbf25712689648c39d9a6bd34bd3d"}, - {file = "tinydb-4.8.0.tar.gz", hash = "sha256:6dd686a9c5a75dfa9280088fd79a419aefe19cd7f4bd85eba203540ef856d564"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -1230,4 +1219,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "2989681acadae96789f699dbadf7345cc7ccd63cd9897ba01b40e406aa264bab" +content-hash = "6e081ffaa7ed614551891ca80d8762eb329c512e791dd0ad265f7469ef894a03" diff --git a/pyproject.toml b/pyproject.toml index d829908..8ca852e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ gidgethub = "^5.2.1" sechat = { git = "https://github.com/GingerIndustries/sechat.git", branch = "v2" } cachetools = "^5.3.1" tomli = "^2.0.1" -tinydb = "^4.7.1" pyjwt = "^2.7.0" python-dateutil = "^2.8.2" websockets = "^10.4" diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index c54a7f2..ab6b35f 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -1,48 +1,23 @@ -import asyncio -from typing import Optional, cast, Any -from time import time +from typing import cast, Any from datetime import datetime from pathlib import Path -from asyncio import create_task, wait_for -from html import unescape -from string import ascii_letters import logging -import sys import json import os -import random -import re -import codecs -import base64 -import subprocess -import tomli -import yaml - -from aiohttp import ClientSession -from aiohttp.web import Application, Request, Response, run_app -from aiohttp.client_exceptions import ContentTypeError -from sechat import Bot, Room, MessageEvent, EventType -from gidgethub import HTTPException as GitHubHTTPException, ValidationError -from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI -from gidgethub.abc import GitHubAPI -from gidgethub.routing import Router -from gidgethub.sansio import Event as GitHubEvent -from gidgethub.apps import get_installation_access_token, get_jwt -from cachetools import LRUCache -from dateutil.parser import parse as parseDatetime -from uwuipy import uwuipy +from aiohttp.web import run_app from discord.utils import setup_logging from motor.motor_asyncio import AsyncIOMotorClient -from vyxalbot2.commands.common import CommonCommands + +import tomli from vyxalbot2.github import GitHubApplication from vyxalbot2.reactions import Reactions -from vyxalbot2.services.discord import DiscordService, VBClient +from vyxalbot2.services.discord import DiscordService from vyxalbot2.services.se import SEService from vyxalbot2.userdb import UserDB -from vyxalbot2.types import CommonData, PublicConfigType, PrivateConfigType, MessagesType, AppToken +from vyxalbot2.types import CommonData, PublicConfigType, PrivateConfigType, MessagesType __version__ = "2.0.0" diff --git a/vyxalbot2/commands/__init__.py b/vyxalbot2/commands/__init__.py index b6a810d..ee73ea2 100644 --- a/vyxalbot2/commands/__init__.py +++ b/vyxalbot2/commands/__init__.py @@ -6,7 +6,6 @@ import re import inspect - from vyxalbot2.types import EventInfo class Command(dict[str, Self]): diff --git a/vyxalbot2/commands/common.py b/vyxalbot2/commands/common.py index da6c01c..63c7c75 100644 --- a/vyxalbot2/commands/common.py +++ b/vyxalbot2/commands/common.py @@ -1,6 +1,5 @@ from datetime import datetime from enum import Enum -from imaplib import Commands from string import ascii_letters import codecs @@ -8,12 +7,11 @@ import subprocess from aiohttp import ClientSession -from discord import Status +from uwuipy import uwuipy from vyxalbot2.commands import CommandSupplier from vyxalbot2.types import CommonData, EventInfo from vyxalbot2.util import RAPTOR -from uwuipy import uwuipy class StatusMood(Enum): MESSAGE = "message" diff --git a/vyxalbot2/commands/se.py b/vyxalbot2/commands/se.py index 598fb32..12e8b6d 100644 --- a/vyxalbot2/commands/se.py +++ b/vyxalbot2/commands/se.py @@ -8,13 +8,12 @@ from aiohttp import ClientSession from gidgethub import BadRequest, ValidationError, HTTPException as GitHubHTTPException from sechat import Room + if TYPE_CHECKING: from vyxalbot2.services.se import SEService from vyxalbot2.commands.common import CommonCommands from vyxalbot2.types import CommonData, EventInfo from vyxalbot2.userdb import User -from urllib.parse import urlparse, urlunparse - from vyxalbot2.util import TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage, resolveChatPFP class SECommands(CommonCommands): diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index f4a9fa0..9f504fb 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -12,10 +12,9 @@ from gidgethub.apps import get_installation_access_token from dateutil.parser import parse as parseDatetime from cachetools import LRUCache -from vyxalbot2.services import PinThat, Service - -import jwt +from jwt import encode as encodeJwt +from vyxalbot2.services import PinThat, Service from vyxalbot2.types import AppToken, PublicConfigType from vyxalbot2.github.formatters import formatIssue, formatRef, formatRepo, formatUser, msgify from vyxalbot2.util import GITHUB_MERGE_QUEUE @@ -68,7 +67,7 @@ def getJwt(self, *, app_id: str, private_key: str) -> str: # This is a copy of gidgethub's get_jwt(), except with the expiry claim decreased a bit time_int = int(time()) payload = {"iat": time_int - 60, "exp": time_int + (7 * 60), "iss": app_id} - bearer_token = jwt.encode(payload, private_key, algorithm="RS256") + bearer_token = encodeJwt(payload, private_key, algorithm="RS256") return bearer_token diff --git a/vyxalbot2/reactions.py b/vyxalbot2/reactions.py index f8f1bc9..2658455 100644 --- a/vyxalbot2/reactions.py +++ b/vyxalbot2/reactions.py @@ -1,14 +1,11 @@ +from itertools import chain, repeat + import random -import typing import re -from itertools import chain, repeat - from vyxalbot2.services import Service - from vyxalbot2.types import EventInfo from vyxalbot2.types import MessagesType -from vyxalbot2.util import RAPTOR OK_TO_SELF_REPLY = ["sus"] DO_NOT_IGNORE_COMMAND_PREFIX = ["sus"] @@ -43,7 +40,6 @@ async def runCommand(self, service: Service, name: str, event: EventInfo, *args) yield line async def onMessage(self, service: Service, event: EventInfo): - didSomething = False for regex, function in MESSAGE_REGEXES.items(): if function not in DO_NOT_IGNORE_COMMAND_PREFIX: reMatch = re.fullmatch(regex, event.content.lower().removeprefix("!!/")) diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py index f573503..a8c4dd5 100644 --- a/vyxalbot2/services/__init__.py +++ b/vyxalbot2/services/__init__.py @@ -1,6 +1,6 @@ from typing import Self, TYPE_CHECKING -from blinker import Namespace, Signal +from blinker import Signal if TYPE_CHECKING: from vyxalbot2.commands import CommandSupplier diff --git a/vyxalbot2/services/discord.py b/vyxalbot2/services/discord.py index 303b21e..120f901 100644 --- a/vyxalbot2/services/discord.py +++ b/vyxalbot2/services/discord.py @@ -1,13 +1,14 @@ -import random from asyncio import get_event_loop import logging import inspect import re +import random -from discord import Client, CustomActivity, Game, Intents, Interaction, Message, Object, TextChannel +from discord import Client, Game, Intents, Interaction, Message, Object, TextChannel from discord.app_commands import CommandTree, Command as DiscordCommand, Group from discord.ext.tasks import loop + from vyxalbot2.commands import Command from vyxalbot2.commands.discord import DiscordCommands from vyxalbot2.services import Service diff --git a/vyxalbot2/services/se/__init__.py b/vyxalbot2/services/se/__init__.py index 2806ccd..67c14e9 100644 --- a/vyxalbot2/services/se/__init__.py +++ b/vyxalbot2/services/se/__init__.py @@ -1,5 +1,5 @@ -from datetime import datetime from typing import cast +from datetime import datetime from urllib.parse import urlparse, urlunparse import random @@ -13,7 +13,6 @@ from markdownify import MarkdownConverter from vyxalbot2.commands.se import SECommands - from vyxalbot2.reactions import Reactions from vyxalbot2.services import PinThat, Service from vyxalbot2.services.se.parser import CommandParser, ParseError diff --git a/vyxalbot2/services/se/parser.py b/vyxalbot2/services/se/parser.py index 9d6eb25..64b2061 100644 --- a/vyxalbot2/services/se/parser.py +++ b/vyxalbot2/services/se/parser.py @@ -1,12 +1,7 @@ -from typing import Any, Callable from enum import Enum, auto from string import digits, ascii_letters - from inspect import signature -from sechat.room import Room -from sechat.events import MessageEvent - from vyxalbot2.commands import Command class ParseState(Enum): diff --git a/vyxalbot2/types.py b/vyxalbot2/types.py index 87a2280..2da2232 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -1,5 +1,4 @@ -from typing import Any, AsyncGenerator, Callable, Optional, TypedDict, TYPE_CHECKING -from enum import Enum, auto +from typing import Any, AsyncGenerator, Callable, TypedDict, TYPE_CHECKING from datetime import datetime from dataclasses import dataclass diff --git a/vyxalbot2/userdb.py b/vyxalbot2/userdb.py index f4404ab..98d95bf 100644 --- a/vyxalbot2/userdb.py +++ b/vyxalbot2/userdb.py @@ -1,15 +1,10 @@ from typing import Optional -from blinker import Signal -from tinydb import TinyDB, Query -from tinydb.table import Document +from blinker import Signal +from odmantic import AIOEngine, Model, ObjectId -from motor.motor_asyncio import AsyncIOMotorClient -from odmantic import AIOEngine, EmbeddedModel, Model, ObjectId from vyxalbot2.services import Service -from vyxalbot2.types import GroupType - class User(Model): service: str serviceIdent: int @@ -19,45 +14,6 @@ class User(Model): linked: dict[str, ObjectId] = {} bonusData: dict[str, str] = {} -class UserDBOld: - def __init__(self, dbPath: str, groupConfig: dict[str, GroupType]): - self._db = TinyDB(dbPath) - self.groupConfig = groupConfig - - def getUserInfo(self, user: int) -> Optional[Document]: - return r[0] if len(r := self._db.search(Query().chatID == user)) else None - - def getUserInfoByName(self, name: str) -> Optional[Document]: - return r[0] if len(r := self._db.search(Query().name == name)) else None - - def addUserToDatabase(self, userData: dict): - self._db.insert( - {"chatID": userData["id"], "name": userData["name"], "groups": []} - ) - - def refreshUserData(self, userData: dict): - self._db.update({"name": userData["name"]}, Query().chatID == userData["id"]) - - def removeUserFromDatabase(self, user: int): - self._db.remove(Query().chatID == user) - - def addUserToGroup(self, user: Document, group: str): - if group in user["groups"]: - return False - user["groups"].append(group) - self._db.update({"groups": user["groups"]}, Query().chatID == user["chatID"]) - return True - - def removeUserFromGroup(self, user: Document, group: str): - user["groups"].remove(group) - self._db.update({"groups": user["groups"]}, Query().chatID == user["chatID"]) - - def membersOfGroup(self, group: str): - return self._db.search(Query().groups.any([group])) - - def users(self): - return self._db.all() - class UserDB: userModify = Signal() def __init__(self, client, database: str): diff --git a/vyxalbot2/util.py b/vyxalbot2/util.py index 3ec71bc..9f824b3 100644 --- a/vyxalbot2/util.py +++ b/vyxalbot2/util.py @@ -1,6 +1,7 @@ -import re from urllib.parse import urlparse, urlunparse +import re + from aiohttp import ClientSession import bs4 From 8023b47adf1cd3ee67d4015cb69cedf3bca60e9b Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:32:02 +0000 Subject: [PATCH 39/42] Add !!/issue close --- vyxalbot2/commands/se.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vyxalbot2/commands/se.py b/vyxalbot2/commands/se.py index 12e8b6d..796ff77 100644 --- a/vyxalbot2/commands/se.py +++ b/vyxalbot2/commands/se.py @@ -194,6 +194,29 @@ async def issueOpenCommand(self, event: EventInfo, repo: str, title: str, body: except BadRequest as e: yield f"Failed to open issue: {e.args}" + async def issueCloseCommand(self, event: EventInfo, repo: str, num: int, body: str=""): + """Close an issue in a repository.""" + if body: + body = body + ( + f"\n\n_Issue closed by {event.userName} [here]" + f'(https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})' + "_" + ) + try: + await self.common.ghClient.gh.post( + f"/repos/{self.common.privateConfig['account']}/{repo}/issues/{num}/comments", + data={"body": body} + ) + except BadRequest as e: + yield f"Failed to send comment: {e.args}" + try: + await self.common.ghClient.gh.patch( + f"/repos/{self.common.privateConfig['account']}/{repo}/issues/{num}", + data={"state": "closed"} + ) + except BadRequest as e: + yield f"Failed to close issue: {e.args}" + async def prodCommand(self, event: EventInfo, repo: str = ""): """Open a PR to update production.""" if len(repo) == 0: From d82dc2f676852ec5a568f9916217e1f82e384820 Mon Sep 17 00:00:00 2001 From: Ginger <75683114+GingerIndustries@users.noreply.github.com> Date: Thu, 30 Nov 2023 07:31:34 -0500 Subject: [PATCH 40/42] what the fuck --- vyxalbot2/github/__init__.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index ccbc11f..9f504fb 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -191,30 +191,6 @@ async def onPushAction(self, event: GitHubEvent): if user == event.data["sender"]["login"]: user = formatUser(event.data["sender"]) yield f"{user} {verb}ed {count} commits ([s]({commits[0]['url']}) [e]({commits[-1]['url']})) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commits[-1]['message'].splitlines()[0]}" - verb = "force-push" if event.data["forced"] else "push" - if len(event.data["commits"]) <= 5: - for commit in event.data["commits"]: - if not commit["distinct"]: - continue - if event.data["pusher"]["name"] == event.data["sender"]["login"]: - user = formatUser(event.data["sender"]) - else: - user = event.data["pusher"]["name"] - yield f"{user} {verb}ed a [commit]({commit['url']}) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commit['message'].splitlines()[0]}" - else: - counter = Counter() - userCommits = defaultdict(lambda: []) - for commit in event.data["commits"]: - if not commit["distinct"]: - continue - name = event.data["pusher"]["name"] - counter[name] += 1 - userCommits[name].append(commit) - for user, count in counter.items(): - commits = userCommits[user] - if user == event.data["sender"]["login"]: - user = formatUser(event.data["sender"]) - yield f"{user} {verb}ed {count} commits ([s]({commits[0]['url']}) [e]({commits[-1]['url']})) to {formatRef(branch, event.data['repository'])} in {formatRepo(event.data['repository'])}: {commits[-1]['message'].splitlines()[0]}" @wrap async def onIssueAction(self, event: GitHubEvent): From 785788ef98999aeca240db9dd7524f586750d51a Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Mon, 4 Dec 2023 13:55:20 +0000 Subject: [PATCH 41/42] ignored no longer --- vyxalbot2/github/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyxalbot2/github/__init__.py b/vyxalbot2/github/__init__.py index ccbc11f..5ff6474 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -28,8 +28,7 @@ async def wrapper(self: "GitHubApplication", event: GitHubEvent, services: list[ if line == PinThat: await service.pin(ids[-1]) continue - # ZWJ so Bridget ignores it - ids.append(await service.send("\u200d" + line, discordSuppressEmbeds=True)) + ids.append(await service.send(line, discordSuppressEmbeds=True)) return wrapper class GitHubApplication(Application): From 87fb60e63703600b039eee343c61522a2886e8ff Mon Sep 17 00:00:00 2001 From: Ginger <75683114+gingerindustries@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:42:09 +0000 Subject: [PATCH 42/42] merge??? --- vyxalbot2/chat/__init__.py | 526 ------------------------------------- 1 file changed, 526 deletions(-) delete mode 100644 vyxalbot2/chat/__init__.py diff --git a/vyxalbot2/chat/__init__.py b/vyxalbot2/chat/__init__.py deleted file mode 100644 index 9e2815f..0000000 --- a/vyxalbot2/chat/__init__.py +++ /dev/null @@ -1,526 +0,0 @@ -import inspect -from time import time -from typing import Callable -from datetime import datetime -from string import ascii_letters - -import re -import random -import codecs -import base64 -import json -import subprocess - -from gidgethub import BadRequest, HTTPException as GitHubHTTPException, ValidationError -from gidgethub.aiohttp import GitHubAPI as AsyncioGitHubAPI -from aiohttp import ClientSession -from sechat import EventType -from tinydb.table import Document -from sechat.room import Room -from sechat.events import MessageEvent, EditEvent -from uwuipy import uwuipy - -import yaml -import logging - -from vyxalbot2.chat.reactions import Reactions -from vyxalbot2.github import GitHubApplication -from vyxalbot2.types import EventInfo - -from ..types import AppToken, PrivateConfigType, PublicConfigType, MessagesType -from .parser import CommandParser, ParseError -from ..userdb import UserDB -from ..util import RAPTOR, TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage - -class Chat: - def __init__(self, room: Room, userDB: UserDB, ghClient: GitHubApplication, session: ClientSession, publicConfig: PublicConfigType, privateConfig: PrivateConfigType, messages: MessagesType, statuses: list[str]): - self.room = room - self.userDB = userDB - self.publicConfig = publicConfig - self.privateConfig = privateConfig - self.messages = messages - self.statuses = statuses - self.ghClient = ghClient - self.session = session - - self.logger = logging.getLogger("Chat") - self.editDB: dict[int, tuple[datetime, list[int]]] = {} - self.commands: dict[str, Callable] = {a: b for a, b in self.genCommands()} - self.commandHelp = self.genCommandHelp() - self.parser = CommandParser(self.commands) - self.errorsSinceStartup = 0 - self.startupTime = datetime.now() - - self.room.register(self.onMessage, EventType.MESSAGE) - self.room.register(self.onEdit, EventType.EDIT) - self.reactions = Reactions(room, self, messages) - - def genCommands(self): - for attrName in self.__dir__(): - attr = getattr(self, attrName) - if not (callable(attr) and hasattr(attr, "__name__")): - continue - if not attr.__name__.lower().endswith("command"): - continue - yield re.sub(r"([A-Z])", lambda match: " " + match.group(0).lower(), attr.__name__.removesuffix("Command")), attr - - def genCommandHelp(self): - help: dict[str, list[str]] = {} - for fullName, impl in self.commands.items(): - if impl.__doc__ is None: - continue - name = fullName.split(" ")[0] - signature = inspect.signature(impl) - parameters = [] - for parameter in signature.parameters.values(): - if parameter.name in ("event", "self"): - continue - if parameter.default is not parameter.empty: - parameters.append(f"[{parameter.name}: {parameter.annotation.__name__}]") - else: - parameters.append(f"<{parameter.name}: {parameter.annotation.__name__}>") - message = (f"`!!/{fullName} " + " ".join(parameters)).strip() + "`: " + impl.__doc__ - if name in help: - help[name].append(message) - else: - help[name] = [message] - return help - - async def onMessage(self, room: Room, message: MessageEvent): - if await self.reactions.onMessage(message): - # A reaction ran, so don't get pissy about invalid commands - return - if message.user_id == self.room.userID: - return - if not message.content.startswith("!!/"): - return - sentAt = datetime.now() - response = [i async for i in self.processMessage(message.content.removeprefix("!!/"), EventInfo(message.user_name, message.user_id, message.message_id))] - if not len(response): - return - responseIDs = [await self.room.reply(message.message_id, response[0])] - for line in response[1:]: - responseIDs.append(await self.room.send(line)) - self.editDB[message.message_id] = (sentAt, responseIDs) - - async def onEdit(self, room: Room, edit: EditEvent): - if edit.user_id == self.room.userID: - return - if not edit.content.startswith("!!/"): - return - if edit.message_id not in self.editDB: - await self.onMessage(room, edit) - else: - sentAt, idents = self.editDB[edit.message_id] - if (datetime.now() - sentAt).seconds > (60 * 2): # margin of error - await self.onMessage(room, edit) - return - response = [i async for i in self.processMessage(edit.content.removeprefix("!!/"), EventInfo(edit.user_name, edit.user_id, edit.message_id))] - if len(response): - response[0] = f":{edit.message_id} " + response[0] - for x in range(min(len(idents), len(response))): - await self.room.edit(idents.pop(0), response.pop(0)) - for leftover in response: - await self.room.send(leftover) - for leftover in idents: - await self.room.delete(leftover) - self.editDB.pop(edit.message_id) - for key, value in self.editDB.copy().items(): - if (datetime.now() - value[0]).seconds > (60 * 2): - self.editDB.pop(key) - - async def processMessage(self, message: str, event: EventInfo): - try: - commandName, impl, args = self.parser.parseCommand(message) - except ParseError as e: - yield "Command error: " + e.message - return - userInfo = self.userDB.getUserInfo(event.userIdent) - for groupName, group in self.publicConfig["groups"].items(): - if commandName in group.get("canRun", []): - if userInfo is not None: - if groupName not in userInfo["groups"]: - yield f"Only members of group {groupName} can run !!/{commandName}." - return - else: - yield f"Only members of group {groupName} can run !!/{commandName}." - return - try: - async for l in impl(event, *args): - yield l - except Exception as e: - yield f"@Ginger An exception occured whilst processing this message!" - self.logger.exception(f"An exception occured whilst processing message {event.messageIdent}:") - - async def dieCommand(self, event: EventInfo): - exit(-42) - - async def helpCommand(self, event: EventInfo, command: str = ""): - """Provide help for a command.""" - if command: - if command == "me": - yield "I'd love to, but I don't have any limbs." - elif command == "syntax": - yield self.messages["syntaxhelp"] - else: - if command in self.commandHelp: - for line in self.commandHelp[command]: - yield line - else: - yield "No help is available for that command." - else: - yield self.messages["help"] + ", ".join(sorted(set(map(lambda i: i.split(" ")[0], self.commands.keys())))) - - async def infoCommand(self, event: EventInfo): - yield self.messages["info"] - - def status(self): - return ( - f"Bot status: Online\n" - f"Uptime: {datetime.now() - self.startupTime}\n" - f"Running since: {self.startupTime.isoformat()}\n" - f"Errors since startup: {self.errorsSinceStartup}" - ) - - async def statusCommand(self, event: EventInfo): - """I will tell you what I'm doing (maybe).""" - status = random.choice(self.statuses) - if not status.endswith(".") and status.endswith(tuple(ascii_letters)): - status += "." - else: - status = status.removesuffix(";") - yield status - - async def statusBoringCommand(self, event: EventInfo): - """Get actual status information about the bot.""" - yield self.status() - - async def statusExcitingCommand(self, event: EventInfo): - yield "\n".join(map(lambda line: line + ("!" * random.randint(2, 5)), self.status().upper().splitlines())) - - async def statusTinglyCommand(self, event: EventInfo): - uwu = uwuipy(None, 0.3, 0.2, 0.2, 1) # type: ignore Me when the developers of uwuipy don't annotate their types correctly - yield uwu.uwuify(self.status()) - - async def statusSleepyCommand(self, event: EventInfo): - status = self.status() - yield ( - "\n".join(status.splitlines())[:random.randint(1, len(status.splitlines()))] - + " *yawn*\n" - + "z" * random.randint(5, 10) - ) - - async def statusCrypticCommand(self, event: EventInfo): - yield codecs.encode(self.status(), "rot13") - - async def statusGoofyCommand(self, event: EventInfo): - yield "\n".join(map(lambda line: line + "🤓" * random.randint(1, 3), self.status().splitlines())) - - def getPermissionsTarget(self, event: EventInfo, name: str) -> Document | str: - if name == "me": - target = self.userDB.getUserInfo(event.userIdent) - if target is None: - return "You are not in my database. Please run !!/register." - else: - target = self.userDB.getUserInfoByName(name) - if target is None: - return "I don't know any user by that name." - return target - - async def permissionsListCommand(self, event: EventInfo, name: str): - """List the groups a user is member of.""" - if isinstance(target := self.getPermissionsTarget(event, name), str): - yield target - return - yield f"User {target['name']} is a member of groups {', '.join(target['groups'])}." - - def permissionsModify(self, event: EventInfo, name: str, group: str, grant: bool): - if isinstance(target := self.getPermissionsTarget(event, name), str): - yield target - return - sender = self.userDB.getUserInfo(event.userIdent) - if sender is None: - yield "You are not in my database. Please run !!/register." - return - group = group.removesuffix("s") - try: - promotionRequires = self.publicConfig["groups"][group].get("promotionRequires", []) - except KeyError: - yield "That group does not exist." - return - if (not any([i in promotionRequires for i in sender["groups"]])) and len(promotionRequires): - yield "Insufficient permissions." - return - if grant: - if self.userDB.addUserToGroup(target, group): - yield f"Added {target['name']} to {group}." - else: - yield f"{target['name']} is already a member of {group}." - else: - if target["chatID"] in self.publicConfig["groups"][group].get("protected", []): - yield "That user may not be removed." - else: - self.userDB.removeUserFromGroup(target, group) - yield f"{target['name']} removed from {group}." - - async def permissionsGrantCommand(self, event: EventInfo, name: str, group: str): - """Add a user to a group.""" - for line in self.permissionsModify(event, name, group, True): - yield line - async def permissionsRevokeCommand(self, event: EventInfo, name: str, group: str): - """Remove a user from a group.""" - for line in self.permissionsModify(event, name, group, False): - yield line - - async def registerCommand(self, event: EventInfo): - """Register yourself to the bot.""" - if self.userDB.getUserInfo(event.userIdent): - yield "You are already registered. If your details are out of date, run !!/refresh." - return - self.userDB.addUserToDatabase( - await ( - await self.session.get( - f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" - ) - ).json() - ) - yield "You have been registered! You don't have any permisssions yet." - - async def refreshCommand(self, event: EventInfo): - """Refresh your user information.""" - if self.userDB.getUserInfo(event.userIdent) is None: - yield "You are not in my database. Please run !!/register." - return - self.userDB.refreshUserData( - await ( - await self.session.get( - f"https://chat.stackexchange.com/users/thumbs/{event.userIdent}" - ) - ).json() - ) - yield "Your details have been updated." - - async def groupsListCommand(self, event: EventInfo): - """List all groups known to the bot.""" - yield "All groups: " + ", ".join(self.publicConfig['groups'].keys()) - async def groupsMembersCommand(self, event: EventInfo, group: str): - """List all members of a group.""" - group = group.removesuffix("s") - yield f"Members of {group}: " + ', '.join(map(lambda i: i['name'], self.userDB.membersOfGroup(group))) - - async def pingCommand(self, event: EventInfo, group: str, message: str): - """Ping all members of a group. Use with care!""" - group = group.removesuffix("s") - pings = " ".join(["@" + target["name"] for target in self.userDB.membersOfGroup(group) if target["chatID"] != event.userIdent]) - if not len(pings): - yield "Nobody to ping." - else: - yield pings + " ^" - - async def coffeeCommand(self, event: EventInfo, target: str = "me"): - """Brew some coffee.""" - if target == "me" or not len(target): - yield "☕" - else: - yield f"@{target} ☕" - - async def maulCommand(self, event: EventInfo, target: str): - """Summon the raptors.""" - if target.lower().removesuffix("2") == "vyxalbot" or target == "me": - yield RAPTOR.format(user=event.userName) - else: - yield RAPTOR.format(user=target) - - async def hugCommand(self, event: EventInfo): - """<3""" - yield random.choice(self.messages["hugs"]) - - async def susCommand(self, event: EventInfo): - """STOP POSTING ABOUT AMONG US""" - yield "āļž" * random.randint(8, 64) - - async def amilyxalCommand(self, event: EventInfo): - yield f"You are {'' if (event.userIdent == 354515) != (random.random() <= 0.1) else 'not '}lyxal." - - async def blameCommand(self, event: EventInfo): - yield f"It was {random.choice(self.userDB.users())['name']}'s fault!" - - async def cookieCommand(self, event: EventInfo): - """Bake a cookie. Maybe. You have to be worthy.""" - if info := self.userDB.getUserInfo(event.userIdent): - if "admin" in info["groups"]: - yield "Here you go: đŸĒ" - else: - if random.random() <= 0.75: - yield "Here you go: đŸĒ" - else: - yield "No." - - async def issueOpenCommand(self, event: EventInfo, repo: str, title: str, body: str, tags: list[str] = []): - """Open an issue in a repository.""" - tagSet = set(tags) - if repo in self.publicConfig["requiredLabels"]: - requiredLabels = self.publicConfig["requiredLabels"][repo] - for rule in requiredLabels["issues"]: - labelSet = set(rule["tags"]) - if rule["exclusive"]: - if len(labelSet.intersection(tagSet)) != 1: - yield f"Must be tagged with exactly one of " + ", ".join(f"`{i}`" for i in labelSet) - return - else: - if len(labelSet.intersection(tagSet)) < 1: - yield f"Must be tagged with one or more of " + ", ".join(f"`{i}`" for i in labelSet) - return - body = body + ( - f"\n\n_Issue created by {event.userName} [here]" - f'(https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})' - "_" - ) - try: - await self.ghClient.gh.post( - f"/repos/{self.privateConfig['account']}/{repo}/issues", - data={ - "title": title, - "body": body, - "labels": tags - }, - oauth_token = await self.ghClient.appToken() - ) - except BadRequest as e: - yield f"Failed to open issue: {e.args}" - - async def issueCloseCommand(self, event: EventInfo, repo: str, num: int, body: str=""): - """Close an issue in a repository.""" - if body: - body = body + ( - f"\n\n_Issue closed by {event.userName} [here]" - f'(https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})' - "_" - ) - try: - await self.ghClient.gh.post( - f"/repos/{self.privateConfig['account']}/{repo}/issues/{num}/comments", - data={'body': body} - ) - except BadRequest as e: - yield f"Failed to send comment: {e.args}" - try: - await self.ghClient.gh.patch( - f"/repos/{self.privateConfig['account']/{repo}/issues/{num}", - data={'state': 'closed'} - ) - except BadRequest as e: - yield "Failed to close issue: {e.args}" - - async def prodCommand(self, event: EventInfo, repo: str = ""): - """Open a PR to update production.""" - if len(repo) == 0: - repo = self.privateConfig["baseRepo"] - if repo not in self.publicConfig["production"]: - yield "Repository not configured." - return - try: - await self.ghClient.gh.post( - f"/repos/{self.privateConfig['account']}/{repo}/pulls", - data={ - "title": f"Update production ({datetime.now().strftime('%b %d %Y')})", - "head": self.publicConfig["production"][repo]["head"], - "base": self.publicConfig["production"][repo]["base"], - "body": f"Requested by {event.userName} [here]({f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent})'}.", - }, - oauth_token=await self.ghClient.appToken() - ) - except ValidationError as e: - yield f"Unable to open PR: {e}" - except GitHubHTTPException as e: - yield f"Failed to create issue: {e.status_code.value} {e.status_code.description}", - - async def idiomAddCommand(self, event: EventInfo, title: str, code: str, description: str, keywords: list[str] = []): - """Add an idiom to the idiom list.""" - file = await self.ghClient.gh.getitem( - f"/repos/{self.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", - oauth_token=await self.ghClient.appToken(), - ) - idioms = yaml.safe_load(base64.b64decode(file["content"])) - if not idioms: - idioms = [] - idioms.append( - { - "name": title, - "code": code, - "description": description, - "link": "#" - + base64.b64encode( - json.dumps(["", "", "", code, ""]).encode( - "utf-8" - ) - ).decode("utf-8"), - "keywords": keywords, - } - ) - await self.ghClient.gh.put( - f"/repos/{self.privateConfig['account']}/vyxal.github.io/contents/src/data/idioms.yaml", - data={ - "message": f"Added \"{title}\" to the idiom list.\nRequested by {event.userName} here: {f'https://chat.stackexchange.com/transcript/{self.room.roomID}?m={event.messageIdent}#{event.messageIdent}'}", - "content": base64.b64encode( - yaml.dump( - idioms, encoding="utf-8", allow_unicode=True - ) - ).decode("utf-8"), - "sha": file["sha"], - }, - oauth_token=await self.ghClient.appToken(), - ) - - async def deliterateifyCommand(self, event: EventInfo, code: str): - """Convert literate code to sbcs""" - async with self.session.post(self.privateConfig["tyxalInstance"] + "/deliterateify", data=code) as response: - if response.status == 400: - yield f"Failed to illiterateify: {await response.text()}" - elif response.status == 200: - yield f"`{await response.text()}`" - else: - yield f"Tyxal sent back an error response! ({response.status})" - # Add an alias - async def delitCommand(self, event: EventInfo, code: str): - async for line in self.deliterateifyCommand(event, code): - yield line - - async def trashCommand(self, event: EventInfo, startRaw: str, endRaw: str, target: int = TRASH): - """Move messages to a room (defaults to Trash).""" - start = extractMessageIdent(startRaw) - end = extractMessageIdent(endRaw) - if start is None: - yield "Malformed start id" - return - if end is None: - yield "Malformed end id" - return - # Sanity check: make sure the messages are actually in our room - if (await getRoomOfMessage(self.session, start)) != self.privateConfig["chat"]["room"]: - yield "Start message does not exist or is not in this room" - return - if (await getRoomOfMessage(self.session, start)) != self.privateConfig["chat"]["room"]: - yield "End message does not exist or is not in this room" - return - # Dubious code to figure out the range of messages we're dealing with - identRange = [i async for i in getMessageRange(self.session, self.privateConfig["chat"]["room"], start, end)] - await self.room.moveMessages(identRange, target) - yield f"Moved {len(identRange)} messages successfully." - - async def pullCommand(self, event: EventInfo): - """Pull changes and restart.""" - if subprocess.run(["git", "pull"]).returncode == 0: - yield "Restarting..." - exit(-43) - else: - yield "Failed to pull!" - - async def commitCommand(self, event: EventInfo): - """Check the commit the bot is running off of""" - result = subprocess.run(["git", "show", "--oneline", "-s", "--no-color"], capture_output=True) - if result.returncode != 0: - yield "Failed to get commit info!" - else: - yield f"Commit: {result.stdout.decode('utf-8').strip()}" -