diff --git a/poetry.lock b/poetry.lock index a67a2e2..e786965 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.0" +version = "3.9.1" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c"}, - {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc"}, - {file = "aiohttp-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30"}, - {file = "aiohttp-3.9.0-cp310-cp310-win32.whl", hash = "sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3"}, - {file = "aiohttp-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192"}, - {file = "aiohttp-3.9.0-cp311-cp311-win32.whl", hash = "sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea"}, - {file = "aiohttp-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8"}, - {file = "aiohttp-3.9.0-cp312-cp312-win32.whl", hash = "sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80"}, - {file = "aiohttp-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887"}, - {file = "aiohttp-3.9.0-cp38-cp38-win32.whl", hash = "sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f"}, - {file = "aiohttp-3.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e"}, - {file = "aiohttp-3.9.0-cp39-cp39-win32.whl", hash = "sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454"}, - {file = "aiohttp-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f"}, - {file = "aiohttp-3.9.0.tar.gz", hash = "sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d"}, + {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] @@ -171,6 +171,7 @@ lxml = ["lxml"] [[package]] name = "black" version = "23.11.0" +version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" @@ -193,6 +194,24 @@ files = [ {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"}, + {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] @@ -210,6 +229,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" @@ -325,34 +355,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] @@ -368,6 +398,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" @@ -463,15 +532,38 @@ 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]] +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" @@ -566,6 +658,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" @@ -614,6 +729,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" @@ -634,6 +801,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" @@ -660,7 +929,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -668,15 +936,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -693,7 +954,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -701,7 +961,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -727,7 +986,7 @@ websockets = "^10.4" type = "git" url = "https://github.com/GingerIndustries/sechat.git" reference = "v2" -resolved_reference = "256a27b3b0ccb71aeba7ca54bed8cd1dedd12ac0" +resolved_reference = "5bf2dc2d820beaf6a56d1009a6422b6e1229a376" [[package]] name = "six" @@ -751,17 +1010,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" @@ -798,6 +1046,8 @@ files = [ [[package]] name = "uwuipy" version = "0.1.7" +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,<4.0" @@ -805,6 +1055,11 @@ files = [ {file = "uwuipy-0.1.7-py3-none-any.whl", hash = "sha256:db26b361e4466c85ae06fe1a663ce77167e16993c9e848511cd49f74107ff1e8"}, {file = "uwuipy-0.1.7.tar.gz", hash = "sha256:94915fbb5f897ab9532d5d39e7e4b726fd8da8c3352efcfb1c1df98a5cf9ec04"}, ] +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" @@ -886,85 +1141,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] @@ -974,4 +1245,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0461349dfb9ee0b6b81b4f60c9f5056ce685d32842f259372856cb1fc4b1712d" +content-hash = "6e081ffaa7ed614551891ca80d8762eb329c512e791dd0ad265f7469ef894a03" diff --git a/pyproject.toml b/pyproject.toml index cd36f86..1cda7f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,15 @@ 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" 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] diff --git a/vyxalbot2/__init__.py b/vyxalbot2/__init__.py index a641fdc..ab6b35f 100644 --- a/vyxalbot2/__init__.py +++ b/vyxalbot2/__init__.py @@ -1,43 +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 + +from aiohttp.web import run_app +from discord.utils import setup_logging +from motor.motor_asyncio import AsyncIOMotorClient 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 uwuivy import uwuipy -from vyxalbot2.github import GitHubApplication +from vyxalbot2.github import GitHubApplication +from vyxalbot2.reactions import Reactions +from vyxalbot2.services.discord import DiscordService +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 __version__ = "2.0.0" @@ -57,39 +37,38 @@ 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"]) + reactions = Reactions(self.messages, self.privateConfig["chat"]["ignore"]) + + 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() + self.se = await SEService.create(reactions, common) + self.discord = await DiscordService.create(reactions, common) + ghApp.services.append(self.se) + ghApp.services.append(self.discord) - 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 +78,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 0f7068a..0000000 --- a/vyxalbot2/chat/__init__.py +++ /dev/null @@ -1,525 +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()}" diff --git a/vyxalbot2/chat/reactions.py b/vyxalbot2/chat/reactions.py deleted file mode 100644 index ab99098..0000000 --- a/vyxalbot2/chat/reactions.py +++ /dev/null @@ -1,97 +0,0 @@ -import random -import typing -import re - -from itertools import chain, repeat - -from sechat import EventType, MessageEvent, Room - -if typing.TYPE_CHECKING: - from vyxalbot2.chat import Chat -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"] -MESSAGE_REGEXES_IN: dict[tuple[str, ...], str] = { - (r"(wh?[au]t( i[sz]|'s)? vyxal\??)", r"what vyxal i[sz]\??"): "info", - (r"(!!/)?(pl(s|z|ease) )?make? meh? (a )?coo?kie?", r"cookie"): "cookie", - (r"((please|pls|plz) )?(make|let|have) velociraptors maul (?P.+)",): "maul", - (r"(make?|brew)( a cup of|some)? coffee for (?P.+)", r"(make?|brew) (?Pme)h?( a)? coffee",): "coffee", - (r"(.* |^)(su+s(sy)?|amon?g ?us|suspicious)( .*|$)",): "sus", - ( - r"(.* |^)([Ww]ho(mst)?|[Ww]hat) (did|done) (that|this|it).*", - r".*whodunit", - ): "blame", - ( - r"(much |very |super |ultra |extremely )*(good|great|excellent|gaming) bot!*", - ): "goodBot", - (r"(hello|hey|hi|howdy|(good )?mornin['g]|(good )?evenin['g])( y'?all)?",): "hello", - (r"((good)?bye|adios|(c|see) ?ya\!?|'night|(good|night )night|\\o)( y'?all)?",): "goodbye", - (r".*mojo.*", ".*đŸ”Ĩ+.*",): "mojo" -} -MESSAGE_REGEXES: dict[str, str] = dict( - chain.from_iterable(zip(k, repeat(v)) for k, v in MESSAGE_REGEXES_IN.items()) -) - -class Reactions: - def __init__(self, room: Room, chat: "Chat", messages: MessagesType): - self.room = room - self.chat = chat - 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 onMessage(self, event: MessageEvent): - 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("!!/")) - 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: - continue - await getattr(self, function)(event, reMatch) - didSomething = True - return didSomething - - async def info(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("info", event) - - async def cookie(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("cookie", event) - - async def coffee(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("coffee", event) - - async def maul(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("maul", event, reMatch.group("user")) - - async def sus(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("sus", event) - - async def blame(self, event: MessageEvent, reMatch: re.Match): - await self.runCommand("blame", event) - - async def goodBot(self, event: MessageEvent, reMatch: re.Match): - await self.room.send(":3") - - async def hello(self, event: MessageEvent, reMatch: re.Match): - await self.room.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 mojo(self, event: MessageEvent, reMatch: re.Match): - emojis = [ - "".join(random.choices(("đŸ¤Ŗ", "😂"), weights=[12, 8], k=random.randint(3, 7))), - "đŸ’¯" * random.choice((1, 3, 5)), - "đŸ”Ĩ" * random.randint(1, 10), - ] - random.shuffle(emojis) - emojis = "".join(emojis) + ("đŸ˜ŗ" * (random.randint(1, 10) == 1)) - await self.room.send(emojis) diff --git a/vyxalbot2/commands/__init__.py b/vyxalbot2/commands/__init__.py new file mode 100644 index 0000000..ee73ea2 --- /dev/null +++ b/vyxalbot2/commands/__init__.py @@ -0,0 +1,62 @@ +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: + 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, 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..63c7c75 --- /dev/null +++ b/vyxalbot2/commands/common.py @@ -0,0 +1,141 @@ +from datetime import datetime +from enum import Enum +from string import ascii_letters + +import codecs +import random +import subprocess + +from aiohttp import ClientSession +from uwuipy import uwuipy + +from vyxalbot2.commands import CommandSupplier +from vyxalbot2.types import CommonData, EventInfo +from vyxalbot2.util import RAPTOR + +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()}" 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..796ff77 --- /dev/null +++ b/vyxalbot2/commands/se.py @@ -0,0 +1,301 @@ +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 vyxalbot2.util import TRASH, extractMessageIdent, getMessageRange, getRoomOfMessage, resolveChatPFP + +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 + + 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"], + 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 = 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 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: + 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 06e3614..99e092a 100644 --- a/vyxalbot2/github/__init__.py +++ b/vyxalbot2/github/__init__.py @@ -1,34 +1,40 @@ +from typing import Optional from collections import Counter, defaultdict -import re 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 jwt import encode as encodeJwt +from vyxalbot2.services import PinThat, Service from vyxalbot2.types import AppToken, PublicConfigType - -from .formatters import formatIssue, formatRef, formatRepo, formatUser, msgify +from vyxalbot2.github.formatters import formatIssue, formatRef, formatRepo, formatUser, msgify from vyxalbot2.util import GITHUB_MERGE_QUEUE def wrap(fun): - async def wrapper(self: "GitHubApplication", event: GitHubEvent, gh: AsyncioGitHubAPI): - async for line in fun(self, event): - await self.room.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, discordSuppressEmbeds=True)) 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 @@ -38,7 +44,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") @@ -60,7 +66,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 @@ -101,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: @@ -110,7 +116,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) @@ -249,16 +256,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/reactions.py b/vyxalbot2/reactions.py new file mode 100644 index 0000000..2658455 --- /dev/null +++ b/vyxalbot2/reactions.py @@ -0,0 +1,96 @@ +from itertools import chain, repeat + +import random +import re + +from vyxalbot2.services import Service +from vyxalbot2.types import EventInfo +from vyxalbot2.types import MessagesType + +OK_TO_SELF_REPLY = ["sus"] +DO_NOT_IGNORE_COMMAND_PREFIX = ["sus"] +MESSAGE_REGEXES_IN: dict[tuple[str, ...], str] = { + (r"(wh?[au]t( i[sz]|'s)? vyxal\??)", r"what vyxal i[sz]\??"): "info", + (r"(!!/)?(pl(s|z|ease) )?make? meh? (a )?coo?kie?", r"cookie"): "cookie", + (r"((please|pls|plz) )?(make|let|have) velociraptors maul (?P.+)",): "maul", + (r"(make?|brew)( a cup of|some)? coffee for (?P.+)", r"(make?|brew) (?Pme)h?( a)? coffee",): "coffee", + (r"(.* |^)(su+s(sy)?|amon?g ?us|suspicious)( .*|$)",): "sus", + ( + r"(.* |^)([Ww]ho(mst)?|[Ww]hat) (did|done) (that|this|it).*", + r".*whodunit", + ): "blame", + ( + r"(much |very |super |ultra |extremely )*(good|great|excellent|gaming) bot!*", + ): "goodBot", + (r"(hello|hey|hi|howdy|(good )?mornin['g]|(good )?evenin['g])( y'?all)?",): "hello", + (r"((good)?bye|adios|(c|see) ?ya\!?|'night|(good|night )night|\\o)( y'?all)?",): "goodbye", + (r".*mojo.*", ".*đŸ”Ĩ+.*",): "mojo" +} +MESSAGE_REGEXES: dict[str, str] = dict( + chain.from_iterable(zip(k, repeat(v)) for k, v in MESSAGE_REGEXES_IN.items()) +) + +class Reactions: + 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): + yield line + + async def onMessage(self, service: Service, event: EventInfo): + for regex, function in MESSAGE_REGEXES.items(): + if function not in DO_NOT_IGNORE_COMMAND_PREFIX: + reMatch = re.fullmatch(regex, event.content.lower().removeprefix("!!/")) + else: + reMatch = re.fullmatch(regex, event.content.lower()) + 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 + + async def info(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "info", event): + yield line + + async def cookie(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "cookie", event): + yield line + + async def coffee(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "coffee", event): + yield line + + async def maul(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "maul", event): + yield line + + async def sus(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "sus", event): + yield line + + async def blame(self, service: Service, event: EventInfo, reMatch: re.Match): + async for line in self.runCommand(service, "blame", event): + yield line + + async def goodBot(self, service: Service, event: EventInfo, reMatch: re.Match): + yield ":3" + + async def hello(self, service: Service, event: EventInfo, reMatch: re.Match): + yield random.choice(self.messages["hello"]) + + async def goodbye(self, service: Service, event: EventInfo, reMatch: re.Match): + yield random.choice(self.messages["goodbye"]) + + 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)), + "đŸ”Ĩ" * random.randint(1, 10), + ] + random.shuffle(emojis) + yield "".join(emojis) + ("đŸ˜ŗ" * (random.randint(1, 10) == 1)) diff --git a/vyxalbot2/services/__init__.py b/vyxalbot2/services/__init__.py new file mode 100644 index 0000000..a8c4dd5 --- /dev/null +++ b/vyxalbot2/services/__init__.py @@ -0,0 +1,38 @@ +from typing import Self, TYPE_CHECKING + +from blinker import Signal + +if TYPE_CHECKING: + from vyxalbot2.commands import CommandSupplier + from vyxalbot2.reactions import Reactions + from vyxalbot2.types import CommonData, EventInfo + +PinThat = object() + +class Service: + messageSignal = Signal() + editSignal = Signal() + commandRequestSignal = Signal() + commandResponseSignal = Signal() + @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, **kwargs) -> int: + 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..120f901 --- /dev/null +++ b/vyxalbot2/services/discord.py @@ -0,0 +1,161 @@ +from asyncio import get_event_loop + +import logging +import inspect +import re +import random + +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 +from vyxalbot2.reactions import Reactions +from vyxalbot2.types import CommandImpl, CommonData, EventInfo + +class VBClient(Client): + 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): + # 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): + 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 + ), + *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) + 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))) + + +class DiscordService(Service): + @classmethod + async def create(cls, reactions: Reactions, common: CommonData): + client = VBClient(common.privateConfig["discord"]["guild"], common.statuses) + await client.login(common.privateConfig["discord"]["token"]) + 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.client.event(self.on_message) + 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() + 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): + if message.author.discriminator == "0000": + return + 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 + ) + 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 + channel = self.client.get_channel(message.channel.id) + 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) + return + await self.messageSignal.send_async(self, event=event) + + + async def shutdown(self): + self.clientTask.cancel() + await self.clientTask + + 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 new file mode 100644 index 0000000..67c14e9 --- /dev/null +++ b/vyxalbot2/services/se/__init__.py @@ -0,0 +1,198 @@ +from typing import cast +from datetime import datetime +from urllib.parse import urlparse, urlunparse + +import random +import logging + +from aiohttp import ClientSession +from bs4 import BeautifulSoup, Tag +from sechat import Bot, EventType +from sechat.room import Room +from sechat.events import MessageEvent, EditEvent +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 +from vyxalbot2.types import CommonData, EventInfo +from vyxalbot2.util import resolveChatPFP + +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.converter = MarkdownConverter(autolinks=False) + + 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]]] = {} + 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 send(self, message: str, **kwargs): + return await self.room.send(message) + + 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] + + 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)) + 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(tag.attrs["src"]) + return cast(str, self.converter.convert_soup(soup)) + + async def onMessage(self, room: Room, message: MessageEvent): + event = EventInfo( + content=self.preprocessMessage(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 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) + for line in reactions: + 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) + 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) + for line in response: + await self.commandResponseSignal.send_async(self, line=line) + + async def onEdit(self, room: Room, edit: EditEvent): + event = EventInfo( + 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 + 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: + 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(), self.commandRequestSignal.muted(): + await self.onMessage(room, edit) + else: + 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): + 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) + + 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}:") \ No newline at end of file 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..64b2061 100644 --- a/vyxalbot2/chat/parser.py +++ b/vyxalbot2/services/se/parser.py @@ -1,11 +1,8 @@ -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): TOPLEVEL = auto() @@ -35,7 +32,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 +151,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 +171,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 +185,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..2da2232 100644 --- a/vyxalbot2/types.py +++ b/vyxalbot2/types.py @@ -1,12 +1,19 @@ -from typing import Optional, TypedDict +from typing import Any, AsyncGenerator, Callable, TypedDict, TYPE_CHECKING 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): @@ -19,7 +26,13 @@ class ChatConfigType(TypedDict): room: int email: str password: str + ignore: list[int] +class DiscordConfigType(TypedDict): + token: str + guild: int + eventChannel: int + bridgeChannel: int class PrivateConfigType(TypedDict): port: int @@ -31,7 +44,11 @@ class PrivateConfigType(TypedDict): webhookSecret: str tyxalInstance: str + mongoUrl: str + database: str + chat: ChatConfigType + discord: DiscordConfigType class AutotagType(TypedDict): issue2pr: dict[str, str] @@ -69,9 +86,23 @@ 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 + 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 638db7c..98d95bf 100644 --- a/vyxalbot2/userdb.py +++ b/vyxalbot2/userdb.py @@ -1,46 +1,47 @@ from typing import Optional -from tinydb import TinyDB, Query -from tinydb.table import Document +from blinker import Signal +from odmantic import AIOEngine, Model, ObjectId -from vyxalbot2.types import GroupType +from vyxalbot2.services import Service +class User(Model): + service: str + serviceIdent: int + name: str + pfp: str + groups: list[str] = [] + linked: dict[str, ObjectId] = {} + bonusData: dict[str, str] = {} class UserDB: - 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() + 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 diff --git a/vyxalbot2/util.py b/vyxalbot2/util.py index ad933ba..9f824b3 100644 --- a/vyxalbot2/util.py +++ b/vyxalbot2/util.py @@ -1,14 +1,29 @@ +from urllib.parse import urlparse, urlunparse + import re 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)