From 921efd738112e7517cec6ca0a6b53149217c7401 Mon Sep 17 00:00:00 2001 From: Mike Hennessy Date: Thu, 9 Sep 2021 13:03:25 -0400 Subject: [PATCH] Format all files using black --- Pipfile | 3 +- Pipfile.lock | 795 ++++++++++++++---------- arq_worker.py | 40 +- bot_start.py | 4 +- oauth_proxy.py | 99 ++- seraphsix/__init__.py | 2 +- seraphsix/bot.py | 152 +++-- seraphsix/cogs/clan.py | 451 ++++++++------ seraphsix/cogs/game.py | 164 ++--- seraphsix/cogs/member.py | 207 +++--- seraphsix/cogs/register.py | 69 +- seraphsix/cogs/server.py | 165 ++--- seraphsix/cogs/utils/checks.py | 56 +- seraphsix/cogs/utils/helpers.py | 11 +- seraphsix/cogs/utils/message_manager.py | 35 +- seraphsix/cogs/utils/paginator.py | 122 ++-- seraphsix/constants.py | 376 ++++++----- seraphsix/database.py | 143 +++-- seraphsix/errors.py | 6 +- seraphsix/models/__init__.py | 15 +- seraphsix/models/database.py | 79 ++- seraphsix/models/destiny.py | 220 ++++--- seraphsix/tasks/activity.py | 301 ++++++--- seraphsix/tasks/clan.py | 125 ++-- seraphsix/tasks/config.py | 136 ++-- seraphsix/tasks/core.py | 110 ++-- seraphsix/tasks/discord.py | 14 +- seraphsix/tasks/parsing.py | 4 +- seraphsix/tasks/the100.py | 25 +- 29 files changed, 2403 insertions(+), 1526 deletions(-) diff --git a/Pipfile b/Pipfile index ca8dad5..511f200 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ migra = {extras = ["pg"],version = "*"} pika = "*" pylama = "*" pyopenssl = "*" +black = "==21.8b0" [packages] aiohttp = ">=3.7.4" @@ -37,7 +38,7 @@ tortoise-orm = {extras = ["asyncpg"], version = "*"} urllib3 = ">=1.26.4" [pipenv] -allow_prereleases = false +allow_prereleases = true [scripts] lint = "python -m pylama" diff --git a/Pipfile.lock b/Pipfile.lock index 9fb1f67..d1ec7b0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b020877e21573fbed7a2313ea49e1c76290cc0c45e9c63f00a3fbb6095dbb22c" + "sha256": "856519af5a4af3be4ddcd903ff1a3005414eb61a3bc66dbecedd785ca2fafb48" }, "pipfile-spec": 6, "requires": {}, @@ -67,19 +67,19 @@ }, "aiosqlite": { "hashes": [ - "sha256:1df802815bb1e08a26c06d5ea9df589bcb8eec56e5f3378103b0f9b223c6703c", - "sha256:2e915463164efa65b60fd1901aceca829b6090082f03082618afca6fb9c8fdf7" + "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231", + "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51" ], "markers": "python_version >= '3.6'", - "version": "==0.16.1" + "version": "==0.17.0" }, "arq": { "hashes": [ - "sha256:340dbc6e47159978714285f4c3fab7ad1c66c65fa26945b5b42f8f0584ed1a84", - "sha256:6883a3048672a69264e23263ae62c1e4559594d7e0d46bd8d4b832b1b6ede840" + "sha256:55a0f933636c804b82c366a0e3710e9e5ed26a716251fa6742777d0b039f7f30", + "sha256:c7bd98151cc83cec941ce5f660ede4bee888effd9a4692258ec8a9a0aff2f9f9" ], "index": "pypi", - "version": "==0.20" + "version": "==0.22" }, "async-timeout": { "hashes": [ @@ -91,23 +91,21 @@ }, "asyncpg": { "hashes": [ - "sha256:11102ac2febbc208427f39e4555537ecf188bd70ef7b285fc92c6c16b748b4c6", - "sha256:255839c8c52ebd72d6d0159564d7eb8f70fcf6cc9ce7cdc7e98328fd3279bf52", - "sha256:2710b5740cbd572e0fddc20986a44707f05d3f84e29fab72abe87fb8c2fc6885", - "sha256:43c44d323c3bd6514fbe6a892ccfdc551259bd92e98dd34ad1a52bad8c7974f3", - "sha256:812dafa4c9e264d430adcc0f5899f0dc5413155a605088af696f952d72d36b5e", - "sha256:98bef539326408da0c2ed0714432e4c79e345820697914318013588ff235b581", - "sha256:a19429d480a387346ae74b38da20e8da004337f14e5066f4bd6a10a8bbe74d3c", - "sha256:a2031df7573c80186339039cc2c4e684648fea5eaa9537c24f18c509bda2cd3f", - "sha256:a88654ede00596a7bdaa08066ff0505aed491f790621dcdb478066c7ddfd1a3d", - "sha256:b784138e69752aaa905b60c5a07a891445706824358fe1440d47113db72c8946", - "sha256:bd6e1f3db9889b5d987b6a1cab49c5b5070756290f3420a4c7a63d942d73ab69", - "sha256:ceedd46f569f5efb8b4def3d1dd6a0d85e1a44722608d68aa1d2d0f8693c1bff", - "sha256:d82d94badd34c8adbc5c85b85085317444cd9e062fc8b956221b34ba4c823b56", - "sha256:df84f3e93cd08cb31a252510a2e7be4bb15e6dff8a06d91f94c057a305d5d55d", - "sha256:f86378bbfbec7334af03bad4d5fd432149286665ecc8bfbcb7135da56b15d34b" - ], - "version": "==0.23.0" + "sha256:129d501f3d30616afd51eb8d3142ef51ba05374256bd5834cec3ef4956a9b317", + "sha256:29ef6ae0a617fc13cc2ac5dc8e9b367bb83cba220614b437af9b67766f4b6b20", + "sha256:41704c561d354bef01353835a7846e5606faabbeb846214dfcf666cf53319f18", + "sha256:556b0e92e2b75dc028b3c4bc9bd5162ddf0053b856437cf1f04c97f9c6837d03", + "sha256:8ff5073d4b654e34bd5eaadc01dc4d68b8a9609084d835acd364cd934190a08d", + "sha256:a458fc69051fbb67d995fdda46d75a012b5d6200f91e17d23d4751482640ed4c", + "sha256:a7095890c96ba36f9f668eb552bb020dddb44f8e73e932f8573efc613ee83843", + "sha256:a738f4807c853623d3f93f0fea11f61be6b0e5ca16ea8aeb42c2c7ee742aa853", + "sha256:c4fc0205fe4ddd5aeb3dfdc0f7bafd43411181e1f5650189608e5971cceacff1", + "sha256:dd2fa063c3344823487d9ddccb40802f02622ddf8bf8a6cc53885ee7a2c1c0c6", + "sha256:ddffcb85227bf39cd1bedd4603e0082b243cf3b14ced64dce506a15b05232b83", + "sha256:e36c6806883786b19551bb70a4882561f31135dc8105a59662e0376cf5b2cbc5", + "sha256:eed43abc6ccf1dc02e0d0efc06ce46a411362f3358847c6b0ec9a43426f91ece" + ], + "version": "==0.24.0" }, "attrs": { "hashes": [ @@ -119,11 +117,11 @@ }, "backoff": { "hashes": [ - "sha256:5e73e2cbe780e1915a204799dba0a01896f45f4385e636bcca7a0614d879d0cd", - "sha256:b8fba021fac74055ac05eb7c7bfce4723aedde6cd0a504e5326bcb0bdd6d19a4" + "sha256:61928f8fa48d52e4faa81875eecf308eccfb1016b018bb6bd21e05b5d90a96c5", + "sha256:ccb962a2378418c667b3c979b504fdeb7d9e0d29c0579e3b13b86467177728cb" ], "index": "pypi", - "version": "==1.10.0" + "version": "==1.11.1" }, "certifi": { "hashes": [ @@ -134,57 +132,53 @@ }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", - "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", - "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", - "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" - ], - "version": "==1.14.5" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "version": "==1.14.6" }, "chardet": { "hashes": [ @@ -194,6 +188,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, + "charset-normalizer": { + "hashes": [ + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" + ], + "markers": "python_version >= '3'", + "version": "==2.0.4" + }, "click": { "hashes": [ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", @@ -204,37 +206,42 @@ }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e", + "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b", + "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", + "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", + "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", + "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", + "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", + "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", + "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", + "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", + "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb", + "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14", + "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af", + "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e", + "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5", + "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06", + "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7" ], "index": "pypi", - "version": "==3.4.7" + "version": "==3.4.8" }, "dataclasses-json": { "hashes": [ - "sha256:740e7b564d72ddaa0f66406b4ecb799447afda2799c1c425a4a76151bfcfda50", - "sha256:fe17da934cfc4ec792ebe7e9a303434ecf4f5f8d8a7705acfbbe7ccbd34bf1ae" + "sha256:381c184e047b738397d8c8fa2f3989a96307149040891ba1b99cd63901c8134c", + "sha256:68e0851451962fa9019958b99af316a0469c371ecfe45c937a4e2f4c43c0ff63" ], "index": "pypi", - "version": "==0.5.3" + "version": "==0.5.5" }, "discord.py": { "hashes": [ - "sha256:114e76cd27362fb919abf7f001a2dbdc77c9a67cff74ed6a89aecd6582ee298e", - "sha256:f179db299c949a8cf0a12c1b1b94d0da9a18e088857154d93ae5ab1d807ec61d" + "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408", + "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c" ], "index": "pypi", - "version": "==1.7.2" + "version": "==1.7.3" }, "flask": { "hashes": [ @@ -315,18 +322,18 @@ }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "iso8601": { "hashes": [ - "sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79", - "sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004" + "sha256:36532f77cc800594e8f16641edae7f1baf7932f05d8e508545b95fc53c6dc85b", + "sha256:906714829fedbc89955d52806c903f2332e3948ed94e31e85037f9e0226b8376" ], - "version": "==0.1.14" + "version": "==0.1.16" }, "itsdangerous": { "hashes": [ @@ -359,30 +366,50 @@ "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", + "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", + "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", + "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", + "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", + "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", @@ -394,11 +421,11 @@ }, "marshmallow": { "hashes": [ - "sha256:8050475b70470cc58f4441ee92375db611792ba39ca1ad41d39cad193ea9e040", - "sha256:b45cde981d1835145257b4a3c5cb7b80786dcf5f50dd2990749a50c16cb48e01" + "sha256:c67929438fd73a2be92128caa0325b1b5ed8b626d91a094d2f7f2771bf1f1c0e", + "sha256:dd4724335d3c2b870b641ffe4a2f8728a1380cd2e7e2312756715ffeaa82b842" ], "markers": "python_version >= '3.5'", - "version": "==3.12.1" + "version": "==3.13.0" }, "marshmallow-enum": { "hashes": [ @@ -493,11 +520,11 @@ }, "peony-twitter": { "hashes": [ - "sha256:2d5f97262eddffa8ccc33e56dc13610362204e95b8640461eb19eaffe78575ec", - "sha256:a91d61fbde20820bbd12f354adc320d92e141209a3ea76a68aa260a516ed5c77" + "sha256:1f29e14b48776ec2858547917793ce7f09c1fc598bafdaf8b21e569e535e8f14", + "sha256:d327e2d6ae54b2107e9288253e0187178ab252a4bb40b334e23fdb108c409fc8" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.0.2" }, "pycparser": { "hashes": [ @@ -572,11 +599,11 @@ }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.25.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" }, "requests-oauth2": { "hashes": [ @@ -601,12 +628,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, - "stringcase": { - "hashes": [ - "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008" - ], - "version": "==1.2.0" - }, "the100": { "git": "https://github.com/henworth/the100", "ref": "f6f39915323e9b3869cf13e269ba72a4596d3352" @@ -616,35 +637,35 @@ "asyncpg" ], "hashes": [ - "sha256:6e5e56694b64118faaada2670343c909d6c9f84c7235f3372b8a398b2e8d6628", - "sha256:99b448a870a81b6edb3ef9d2f0e22b2c83afa2b6348178840d3ccdbf03e206d3" + "sha256:36bb0d1f9bd800d3b91d5490cfc13a598c0e0f570a638b185bc4976abfabd135", + "sha256:74d5c6341fc0e2d4ef7a321f460107715da2679a2b3e2a48e32e276ed7bd3fc0" ], "index": "pypi", - "version": "==0.17.3" + "version": "==0.17.7" }, "typing-extensions": { "hashes": [ - "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", - "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", - "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" ], - "version": "==3.10.0.0" + "version": "==3.10.0.2" }, "typing-inspect": { "hashes": [ - "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f", - "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7", - "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0" + "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa", + "sha256:3cd7d4563e997719a710a3bfe7ffb544c6b72069b6812a02e9b414a8fa3aaa6b", + "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5" ], - "version": "==0.6.0" + "version": "==0.7.1" }, "urllib3": { "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], "index": "pypi", - "version": "==1.26.5" + "version": "==1.26.6" }, "werkzeug": { "hashes": [ @@ -701,19 +722,19 @@ "develop": { "aio-pika": { "hashes": [ - "sha256:1d4305a5f78af3857310b4fe48348cdcf6c097e0e275ea88c2cd08570531a369", - "sha256:e69afef8695f47c5d107bbdba21bdb845d5c249acb3be53ef5c2d497b02657c0" + "sha256:1dd5bfefa25bd3581f0b6dedc2904b7d7f810be18ec67a1575a951b6339d7784", + "sha256:cd0a82546676ea52cb7f0e5636a1e215195045f7021012eba4d91936a4c2b245" ], "index": "pypi", - "version": "==6.8.0" + "version": "==7.0.0b3" }, "aiormq": { "hashes": [ - "sha256:8218dd9f7198d6e7935855468326bbacf0089f926c70baa8dd92944cb2496573", - "sha256:e584dac13a242589aaf42470fd3006cb0dc5aed6506cbd20357c7ec8bbe4a89e" + "sha256:04deada9000210b94d4fe28eed668283f1e3f0f27e6769ff64929076a113a0dd", + "sha256:7d2291ce142841b992c8940158b8a87c7ecfc848a87c2021c51ca7ec92e1ce4f" ], - "markers": "python_version >= '3.6'", - "version": "==3.3.1" + "markers": "python_version >= '3.7'", + "version": "==5.1.0" }, "autopep8": { "hashes": [ @@ -723,140 +744,158 @@ "index": "pypi", "version": "==1.5.7" }, + "black": { + "hashes": [ + "sha256:2a0f9a8c2b2a60dbcf1ccb058842fb22bdbbcb2f32c6cc02d9578f90b92ce8b7", + "sha256:570608d28aa3af1792b98c4a337dbac6367877b47b12b88ab42095cfc1a627c2" + ], + "index": "pypi", + "version": "==21.8b0" + }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", - "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", - "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", - "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" - ], - "version": "==1.14.5" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "version": "==1.14.6" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e", + "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b", + "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7", + "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085", + "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc", + "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a", + "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498", + "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9", + "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c", + "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7", + "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb", + "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14", + "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af", + "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e", + "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5", + "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06", + "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7" ], "index": "pypi", - "version": "==3.4.7" + "version": "==3.4.8" }, "greenlet": { "hashes": [ - "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c", - "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832", - "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08", - "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e", - "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22", - "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f", - "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c", - "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea", - "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8", - "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad", - "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc", - "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16", - "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8", - "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5", - "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99", - "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e", - "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a", - "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56", - "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c", - "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed", - "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959", - "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922", - "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927", - "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e", - "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a", - "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131", - "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919", - "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319", - "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae", - "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535", - "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505", - "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11", - "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47", - "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821", - "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857", - "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da", - "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc", - "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5", - "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb", - "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05", - "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5", - "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee", - "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e", - "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831", - "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f", - "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3", - "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6", - "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3", - "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f" + "sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3", + "sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16", + "sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c", + "sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f", + "sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45", + "sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b", + "sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384", + "sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c", + "sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f", + "sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1", + "sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc", + "sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68", + "sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142", + "sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92", + "sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83", + "sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e", + "sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c", + "sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d", + "sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687", + "sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117", + "sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6", + "sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd", + "sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b", + "sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649", + "sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3", + "sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c", + "sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378", + "sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6", + "sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272", + "sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e", + "sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5", + "sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04", + "sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885", + "sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf", + "sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662", + "sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722", + "sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de", + "sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837", + "sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481", + "sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7", + "sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629", + "sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f", + "sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663", + "sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362", + "sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b", + "sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334", + "sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4", + "sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c", + "sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563", + "sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1" ], - "markers": "python_version >= '3'", - "version": "==1.1.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.1.1" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "mccabe": { "hashes": [ @@ -919,20 +958,34 @@ "markers": "python_version >= '3.6'", "version": "==5.1.0" }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" + "markers": "python_version >= '3.6'", + "version": "==21.0" }, "pamqp": { "hashes": [ - "sha256:2f81b5c186f668a67f165193925b6bfd83db4363a6222f599517f29ecee60b02", - "sha256:5cd0f5a85e89f20d5f8e19285a1507788031cfca4a9ea6f067e3cf18f5e294e8" + "sha256:0a9b49bde3f554ec49b47ebdb789133979985f24d5f4698935ed589a2d4392a4" ], - "version": "==2.3.0" + "markers": "python_version >= '3.6'", + "version": "==3.0.1" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" }, "pika": { "hashes": [ @@ -942,45 +995,47 @@ "index": "pypi", "version": "==1.2.0" }, + "platformdirs": { + "hashes": [ + "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f", + "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.0" + }, "psycopg2-binary": { "hashes": [ - "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", - "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", - "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", - "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", - "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", - "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", - "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", - "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", - "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", - "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", - "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", - "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", - "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", - "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", - "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", - "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", - "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", - "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", - "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", - "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", - "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", - "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", - "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", - "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", - "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", - "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", - "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", - "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", - "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", - "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", - "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", - "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", - "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", - "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", - "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" - ], - "version": "==2.8.6" + "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975", + "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd", + "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616", + "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2", + "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90", + "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a", + "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e", + "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d", + "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed", + "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a", + "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140", + "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32", + "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31", + "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a", + "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917", + "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf", + "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7", + "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0", + "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72", + "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698", + "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773", + "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68", + "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76", + "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4", + "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f", + "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34", + "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce", + "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a", + "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e" + ], + "version": "==2.9.1" }, "pycodestyle": { "hashes": [ @@ -1032,11 +1087,57 @@ }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:14e99e14e11a14cadf4d99effd4c5c30a9f46b116551ee69b4e5f8f6004f62d5", + "sha256:61b247121581f50c7988eece6ebb2d87ccc3be46c5552daf910d56e20cf6da75" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.5'", + "version": "==3.0.0rc1" + }, + "regex": { + "hashes": [ + "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468", + "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354", + "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308", + "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d", + "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc", + "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8", + "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797", + "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2", + "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13", + "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d", + "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a", + "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0", + "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73", + "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1", + "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed", + "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a", + "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b", + "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f", + "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256", + "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb", + "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2", + "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983", + "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb", + "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645", + "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8", + "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a", + "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906", + "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f", + "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c", + "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892", + "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0", + "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e", + "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e", + "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed", + "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c", + "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374", + "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd", + "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791", + "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a", + "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1", + "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759" + ], + "version": "==2021.8.28" }, "schemainspect": { "hashes": [ @@ -1063,39 +1164,39 @@ }, "sqlalchemy": { "hashes": [ - "sha256:196fb6bb2733834e506c925d7532f8eabad9d2304deef738a40846e54c31e236", - "sha256:1dd77acbc19bee9c0ba858ff5e4e5d5c60895495c83b4df9bcdf4ad5e9b74f21", - "sha256:216ff28fe803885ceb5b131dcee6507d28d255808dd5bcffcb3b5fa75be2e102", - "sha256:461a4ea803ce0834822f372617a68ac97f9fa1281f2a984624554c651d7c3ae1", - "sha256:4b09191ed22af149c07a880f309b7740f3f782ff13325bae5c6168a6aa57e715", - "sha256:4c5e20666b33b03bf7f14953f0deb93007bf8c1342e985bd7c7cf25f46fac579", - "sha256:4d93b62e98248e3e1ac1e91c2e6ee1e7316f704be1f734338b350b6951e6c175", - "sha256:5732858e56d32fa7e02468f4fd2d8f01ddf709e5b93d035c637762890f8ed8b6", - "sha256:58c02d1771bb0e61bc9ced8f3b36b5714d9ece8fd4bdbe2a44a892574c3bbc3c", - "sha256:651cdb3adcee13624ba22d5ff3e96f91e16a115d2ca489ddc16a8e4c217e8509", - "sha256:6fe1c8dc26bc0005439cb78ebc78772a22cccc773f5a0e67cb3002d791f53f0f", - "sha256:7222f3236c280fab3a2d76f903b493171f0ffc29667538cc388a5d5dd0216a88", - "sha256:7dc3d3285fb682316d580d84e6e0840fdd8ffdc05cb696db74b9dd746c729908", - "sha256:7e45043fe11d503e1c3f9dcf5b42f92d122a814237cd9af68a11dae46ecfcae1", - "sha256:7eb55d5583076c03aaf1510473fad2a61288490809049cb31028af56af7068ee", - "sha256:82922a320d38d7d6aa3a8130523ec7e8c70fa95f7ca7d0fd6ec114b626e4b10b", - "sha256:8e133e2551fa99c75849848a4ac08efb79930561eb629dd7d2dc9b7ee05256e6", - "sha256:949ac299903d2ed8419086f81847381184e2264f3431a33af4679546dcc87f01", - "sha256:a2d225c8863a76d15468896dc5af36f1e196b403eb9c7e0151e77ffab9e7df57", - "sha256:a5f00a2be7d777119e15ccfb5ba0b2a92e8a193959281089d79821a001095f80", - "sha256:b0ad951a6e590bbcfbfeadc5748ef5ec8ede505a8119a71b235f7481cc08371c", - "sha256:b59b2c0a3b1d93027f6b6b8379a50c354483fe1ebe796c6740e157bb2e06d39a", - "sha256:bc89e37c359dcd4d75b744e5e81af128ba678aa2ecea4be957e80e6e958a1612", - "sha256:bde055c019e6e449ebc4ec61abd3e08690abeb028c7ada2a3b95d8e352b7b514", - "sha256:c367ed95d41df584f412a9419b5ece85b0d6c2a08a51ae13ae47ef74ff9a9349", - "sha256:dde05ae0987e43ec84e64d6722ce66305eda2a5e2b7d6fda004b37aabdfbb909", - "sha256:ee6e7ca09ff274c55d19a1e15ee6f884fa0230c0d9b8d22a456e249d08dee5bf", - "sha256:f1c68f7bd4a57ffdb85eab489362828dddf6cd565a4c18eda4c446c1d5d3059d", - "sha256:f63e1f531a8bf52184e2afb53648511f3f8534decb7575b483a583d3cd8d13ed", - "sha256:fdad4a33140b77df61d456922b7974c1f1bb2c35238f6809f078003a620c4734" + "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d", + "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090", + "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9", + "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f", + "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff", + "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0", + "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f", + "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389", + "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af", + "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a", + "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81", + "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0", + "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6", + "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f", + "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd", + "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4", + "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583", + "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787", + "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90", + "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262", + "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8", + "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00", + "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67", + "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4", + "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9", + "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1", + "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5", + "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7", + "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194", + "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.4.17" + "version": "==1.4.23" }, "sqlbag": { "hashes": [ @@ -1112,6 +1213,22 @@ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, + "tomli": { + "hashes": [ + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e", + "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7", + "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + ], + "version": "==3.10.0.2" + }, "yarl": { "hashes": [ "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", diff --git a/arq_worker.py b/arq_worker.py index db0f3e6..54a05b2 100644 --- a/arq_worker.py +++ b/arq_worker.py @@ -9,8 +9,12 @@ from seraphsix.database import Database from seraphsix.models import deserializer, serializer from seraphsix.tasks.activity import ( - get_characters, process_activity, store_member_history, store_last_active, store_all_games, - save_last_active + get_characters, + process_activity, + store_member_history, + store_last_active, + store_all_games, + save_last_active, ) from seraphsix.tasks.core import set_cached_members from seraphsix.tasks.config import Config, log_config @@ -19,7 +23,7 @@ async def startup(ctx): - ctx['destiny'] = Pydest( + ctx["destiny"] = Pydest( api_key=config.destiny.api_key, client_id=config.destiny.client_id, client_secret=config.destiny.client_secret, @@ -27,25 +31,29 @@ async def startup(ctx): database = Database(config.database_url, config.database_conns) await database.initialize() - ctx['database'] = database - ctx['redis_cache'] = await aioredis.create_redis_pool(config.redis_url) - ctx['redis_jobs'] = ctx['redis'] + ctx["database"] = database + ctx["redis_cache"] = await aioredis.create_redis_pool(config.redis_url) + ctx["redis_jobs"] = ctx["redis"] async def shutdown(ctx): - await ctx['destiny'].close() - if 'database' in ctx: - await ctx['database'].close() - if 'redis_cache' in ctx: - ctx['redis_cache'].close() - await ctx['redis_cache'].wait_closed() + await ctx["destiny"].close() + if "database" in ctx: + await ctx["database"].close() + if "redis_cache" in ctx: + ctx["redis_cache"].close() + await ctx["redis_cache"].wait_closed() class WorkerSettings: functions = [ - set_cached_members, get_characters, process_activity, - store_member_history, store_all_games, - func(save_last_active, keep_result=240), func(store_last_active, keep_result=240) + set_cached_members, + get_characters, + process_activity, + store_member_history, + store_all_games, + func(save_last_active, keep_result=240), + func(store_last_active, keep_result=240), ] on_startup = startup on_shutdown = shutdown @@ -60,7 +68,7 @@ def job_deserializer(b): return deserializer(b) -if __name__ == '__main__': +if __name__ == "__main__": logging.config.dictConfig(log_config()) worker = Worker(**get_kwargs(WorkerSettings)) worker.run() diff --git a/bot_start.py b/bot_start.py index b339952..09e60f1 100644 --- a/bot_start.py +++ b/bot_start.py @@ -13,10 +13,10 @@ def main(): bot = SeraphSix(config) bot.run(config.discord_api_key) except Exception: - logging.config.dictConfig(log_config('DEBUG')) + logging.config.dictConfig(log_config("DEBUG")) log = logging.getLogger(__name__) log.exception("Caught exception") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/oauth_proxy.py b/oauth_proxy.py index c252836..e9a95c2 100644 --- a/oauth_proxy.py +++ b/oauth_proxy.py @@ -24,98 +24,97 @@ class DestinyClient(OAuth2): - site = 'https://www.bungie.net' - authorization_url = '/en/oauth/authorize/' - token_url = '/platform/app/oauth/token/' + site = "https://www.bungie.net" + authorization_url = "/en/oauth/authorize/" + token_url = "/platform/app/oauth/token/" destiny_auth = DestinyClient( client_id=config.destiny.client_id, client_secret=config.destiny.client_secret, - redirect_uri=f'https://{config.destiny.redirect_host}/oauth/callback' + redirect_uri=f"https://{config.destiny.redirect_host}/oauth/callback", ) -@app.route('/') +@app.route("/") def index(): - session['code'] = request.args.get('code') + session["code"] = request.args.get("code") - if not session.get('access_token'): - log.debug(f'No access_token found in session, redirecting to /oauth, {session}') - return redirect( - url_for('oauth_index') - ) + if not session.get("access_token"): + log.debug(f"No access_token found in session, redirecting to /oauth, {session}") + return redirect(url_for("oauth_index")) user_info = dict( - membership_id=session.get('membership_id'), - access_token=session.get('access_token'), - refresh_token=session.get('refresh_token') + membership_id=session.get("membership_id"), + access_token=session.get("access_token"), + refresh_token=session.get("refresh_token"), ) pickled_info = pickle.dumps(user_info) try: - red.publish(session['state'], pickled_info) + red.publish(session["state"], pickled_info) except Exception: - log.exception(f'/: Failed to publish state info to redis: {user_info} {session}') - return render_template('message.html', message='Something went wrong.') - return render_template('redirect.html', site=DestinyClient.site, message='Success!') + log.exception( + f"/: Failed to publish state info to redis: {user_info} {session}" + ) + return render_template("message.html", message="Something went wrong.") + return render_template("redirect.html", site=DestinyClient.site, message="Success!") -@app.route('/oauth') +@app.route("/oauth") def oauth_index(): - session['state'] = request.args.get('state') + session["state"] = request.args.get("state") - if not session.get('access_token'): - log.debug(f'No access_token found in session, redirecting to /oauth/callback, {session}') - return redirect( - url_for('oauth_callback') + if not session.get("access_token"): + log.debug( + f"No access_token found in session, redirecting to /oauth/callback, {session}" ) + return redirect(url_for("oauth_callback")) with requests.Session() as s: - s.auth = OAuth2BearerToken(session['access_token']) - s.headers.update({'X-API-KEY': config.destiny.api_key}) - r = s.get(f'{DestinyClient.site}/platform/User/GetMembershipsForCurrentUser/') + s.auth = OAuth2BearerToken(session["access_token"]) + s.headers.update({"X-API-KEY": config.destiny.api_key}) + r = s.get(f"{DestinyClient.site}/platform/User/GetMembershipsForCurrentUser/") r.raise_for_status() - log.debug(f'/oauth: {session} {request.args}') - return redirect('/') + log.debug(f"/oauth: {session} {request.args}") + return redirect("/") -@app.route('/oauth/callback') +@app.route("/oauth/callback") def oauth_callback(): - code = request.args.get('code') - error = request.args.get('error') + code = request.args.get("code") + error = request.args.get("error") if error: log.error(repr(error)) - return render_template('message.html', message='Something went wrong.') + return render_template("message.html", message="Something went wrong.") if not code: - log.debug(f'No code found, redirecting to bungie, {session}') - return redirect(destiny_auth.authorize_url( - response_type='code', - state=session['state'] - )) + log.debug(f"No code found, redirecting to bungie, {session}") + return redirect( + destiny_auth.authorize_url(response_type="code", state=session["state"]) + ) data = destiny_auth.get_token( code=code, - grant_type='authorization_code', + grant_type="authorization_code", ) - session['code'] = code - session['access_token'] = data.get('access_token') - session['refresh_token'] = data.get('refresh_token') - session['membership_id'] = data.get('membership_id') - log.debug(f'/oauth/callback: {session} {request.args}') - return redirect(url_for('index')) + session["code"] = code + session["access_token"] = data.get("access_token") + session["refresh_token"] = data.get("refresh_token") + session["membership_id"] = data.get("membership_id") + log.debug(f"/oauth/callback: {session} {request.args}") + return redirect(url_for("index")) -@app.route('/the100webhook//slack', methods=['POST']) +@app.route("/the100webhook//slack", methods=["POST"]) def the100_webhook(guild_id): data = request.get_json(force=True) - log.info(f'{guild_id} {data}') - return render_template('message.html', message='Success!') + log.info(f"{guild_id} {data}") + return render_template("message.html", message="Success!") -if __name__ == '__main__': - app.run(debug=True, ssl_context='adhoc') +if __name__ == "__main__": + app.run(debug=True, ssl_context="adhoc") diff --git a/seraphsix/__init__.py b/seraphsix/__init__.py index 37995f6..9074538 100644 --- a/seraphsix/__init__.py +++ b/seraphsix/__init__.py @@ -1,3 +1,3 @@ from seraphsix.database import Database -__all__ = ['Database'] +__all__ = ["Database"] diff --git a/seraphsix/bot.py b/seraphsix/bot.py index 8334260..5803598 100644 --- a/seraphsix/bot.py +++ b/seraphsix/bot.py @@ -15,8 +15,14 @@ from seraphsix import constants, Database from seraphsix.cogs.utils.message_manager import MessageManager from seraphsix.errors import ( - InvalidCommandError, InvalidGameModeError, InvalidMemberError, InvalidAdminError, - NotRegisteredError, ConfigurationError, MissingTimezoneError, MaintenanceError, + InvalidCommandError, + InvalidGameModeError, + InvalidMemberError, + InvalidAdminError, + NotRegisteredError, + ConfigurationError, + MissingTimezoneError, + MaintenanceError, ) from seraphsix.models.database import Guild, TwitterChannel from seraphsix.tasks.clan import ack_clan_application @@ -29,8 +35,11 @@ intents.reactions = True STARTUP_EXTENSIONS = [ - 'seraphsix.cogs.clan', 'seraphsix.cogs.game', 'seraphsix.cogs.member', - 'seraphsix.cogs.register', 'seraphsix.cogs.server' + "seraphsix.cogs.clan", + "seraphsix.cogs.game", + "seraphsix.cogs.member", + "seraphsix.cogs.register", + "seraphsix.cogs.server", ] @@ -38,7 +47,7 @@ async def _prefix_callable(bot, message): """Get current command prefix""" base = [f"<@{bot.user.id}> "] if isinstance(message.channel, discord.abc.PrivateChannel): - base.append('?') + base.append("?") else: guild_db, _ = await Guild.get_or_create(guild_id=message.guild.id) base.append(guild_db.prefix) @@ -46,12 +55,14 @@ async def _prefix_callable(bot, message): class SeraphSix(commands.Bot): - def __init__(self, config): super().__init__( - command_prefix=_prefix_callable, case_insensitive=True, intents=intents, + command_prefix=_prefix_callable, + case_insensitive=True, + intents=intents, help_command=commands.DefaultHelpCommand( - no_category="Assorted", dm_help=True, verify_checks=False) + no_category="Assorted", dm_help=True, verify_checks=False + ), ) self.config = config @@ -66,17 +77,21 @@ def __init__(self, config): self.the100 = The100(config.the100.api_key, config.the100.base_url) self.twitter = None - if (config.twitter.consumer_key and config.twitter.consumer_secret and - config.twitter.access_token and config.twitter.access_token_secret): + if ( + config.twitter.consumer_key + and config.twitter.consumer_secret + and config.twitter.access_token + and config.twitter.access_token_secret + ): self.twitter = PeonyClient(**config.twitter.asdict()) self.ext_conns = { - 'database': self.database, - 'destiny': self.destiny, - 'twitter': self.twitter, - 'the100': self.the100, - 'redis_cache': None, - 'redis_jobs': None + "database": self.database, + "destiny": self.destiny, + "twitter": self.twitter, + "the100": self.the100, + "redis_cache": None, + "redis_jobs": None, } for extension in STARTUP_EXTENSIONS: @@ -101,14 +116,24 @@ async def update_members(self): discord_guild = await self.fetch_guild(guild_id) guild_name = str(discord_guild) - log.info(f"Queueing task to find last active date for all members of {guild_name} ({guild_id})") - await self.ext_conns['redis_jobs'].enqueue_job( - 'store_last_active', guild_id, guild_name, _job_id=f'store_last_active-{guild_id}' + log.info( + f"Queueing task to find last active date for all members of {guild_name} ({guild_id})" + ) + await self.ext_conns["redis_jobs"].enqueue_job( + "store_last_active", + guild_id, + guild_name, + _job_id=f"store_last_active-{guild_id}", ) - log.info(f"Queueing task to find recent games for all members {guild_name} ({guild_id})") - await self.ext_conns['redis_jobs'].enqueue_job( - 'store_all_games', guild_id, guild_name, _job_id=f'store_all_games-{guild_id}' + log.info( + f"Queueing task to find recent games for all members {guild_name} ({guild_id})" + ) + await self.ext_conns["redis_jobs"].enqueue_job( + "store_all_games", + guild_id, + guild_name, + _job_id=f"store_all_games-{guild_id}", ) @update_members.before_loop @@ -127,7 +152,8 @@ async def process_tweet(self, tweet): if not channels: log.info( - f"Could not find any Discord channels for {tweet.user.screen_name} ({tweet.user.id})") + f"Could not find any Discord channels for {tweet.user.screen_name} ({tweet.user.id})" + ) return twitter_url = f"https://twitter.com/{tweet.user.screen_name}/status/{tweet.id}" @@ -139,7 +165,9 @@ async def process_tweet(self, tweet): await channel.send(twitter_url) async def track_tweets(self): - stream = self.twitter.stream.statuses.filter.post(follow=constants.TWITTER_FOLLOW_USERS) + stream = self.twitter.stream.statuses.filter.post( + follow=constants.TWITTER_FOLLOW_USERS + ) async for tweet in stream: if peony.events.tweet(tweet) and not peony.events.retweet(tweet): if tweet.in_reply_to_status_id: @@ -151,8 +179,8 @@ async def track_tweets(self): async def connect_redis(self): self.redis = await aioredis.create_redis_pool(self.config.redis_url) - self.ext_conns['redis_cache'] = self.redis - self.ext_conns['redis_jobs'] = await create_redis_jobs_pool() + self.ext_conns["redis_cache"] = self.redis + self.ext_conns["redis_jobs"] = await create_redis_jobs_pool() @tasks.loop(hours=1.0) async def cache_clan_members(self): @@ -163,9 +191,14 @@ async def cache_clan_members(self): guild_id = guild.guild_id discord_guild = await self.fetch_guild(guild.guild_id) guild_name = str(discord_guild) - log.info(f"Queueing task to update cached members of {guild_name} ({guild_id})") - await self.ext_conns['redis_jobs'].enqueue_job( - 'set_cached_members', guild_id, guild_name, _job_id=f'set_cached_members-{guild_id}' + log.info( + f"Queueing task to update cached members of {guild_name} ({guild_id})" + ) + await self.ext_conns["redis_jobs"].enqueue_job( + "set_cached_members", + guild_id, + guild_name, + _job_id=f"set_cached_members-{guild_id}", ) @cache_clan_members.before_loop @@ -205,8 +238,12 @@ async def on_member_update(self, before, after): await update_sherpa(self, before, after) async def on_raw_reaction_add(self, payload): - if payload.channel_id == self.guild_map[payload.guild_id].admin_channel and \ - payload.emoji.name in [constants.EMOJI_CHECKMARK, constants.EMOJI_CROSSMARK]: + if payload.channel_id == self.guild_map[ + payload.guild_id + ].admin_channel and payload.emoji.name in [ + constants.EMOJI_CHECKMARK, + constants.EMOJI_CROSSMARK, + ]: await ack_clan_application(self, payload) async def on_guild_join(self, guild): @@ -221,18 +258,28 @@ async def on_command_error(self, ctx, error): text = None if isinstance(error, commands.MissingPermissions): text = "Sorry, but you do not have permissions to do that!" - elif isinstance(error, ( - ConfigurationError, InvalidCommandError, InvalidMemberError, - InvalidGameModeError, NotRegisteredError, MissingTimezoneError, - MaintenanceError, InvalidAdminError - )): + elif isinstance( + error, + ( + ConfigurationError, + InvalidCommandError, + InvalidMemberError, + InvalidGameModeError, + NotRegisteredError, + MissingTimezoneError, + MaintenanceError, + InvalidAdminError, + ), + ): text = error elif isinstance(error, commands.CommandNotFound): text = f"Invalid command `{ctx.message.content}`." elif isinstance(error, commands.MissingRequiredArgument): text = f"Required argument `{error.param}` is missing." else: - error_trace = traceback.format_exception(type(error), error, error.__traceback__) + error_trace = traceback.format_exception( + type(error), error, error.__traceback__ + ) log.error(f"Ignoring exception in command '{ctx.command}': {error_trace}") if ctx.guild: location = f"guild `{ctx.guild.id}`" @@ -244,18 +291,23 @@ async def on_command_error(self, ctx, error): ) await self.log_channel.send( content=log_channel_message, - file=discord.File(io.BytesIO(''.join(error_trace).encode('utf-8')), filename="exception.txt") + file=discord.File( + io.BytesIO("".join(error_trace).encode("utf-8")), + filename="exception.txt", + ), ) await manager.send_message( ( f"Unexpected error occurred while running `{ctx.command}`. " f"Details have been dispatched to the development team." ), - clean=False + clean=False, ) if text: - await manager.send_and_clean(f"{text}\nType `{ctx.prefix}help` for more information.") + await manager.send_and_clean( + f"{text}\nType `{ctx.prefix}help` for more information." + ) # Update guild count at bot listing sites and in bots status/presence # async def update_status(self): @@ -270,22 +322,24 @@ async def on_message(self, message): ctx = await self.get_context(message) await self.invoke(ctx) except AttributeError as error: - error_trace = traceback.format_exception(type(error), error, error.__traceback__) + error_trace = traceback.format_exception( + type(error), error, error.__traceback__ + ) log.error(f"Ignoring exception from message '{message}': {error_trace}") async def close(self): await self.log_channel.send("Seraph Six is shutting down...") - await self.ext_conns['destiny'].close() - await self.ext_conns['database'].close() - await self.ext_conns['the100'].close() + await self.ext_conns["destiny"].close() + await self.ext_conns["database"].close() + await self.ext_conns["the100"].close() if self.twitter: - await self.ext_conns['twitter'].close() + await self.ext_conns["twitter"].close() - self.ext_conns['redis_jobs'].close() - await self.ext_conns['redis_jobs'].wait_closed() + self.ext_conns["redis_jobs"].close() + await self.ext_conns["redis_jobs"].wait_closed() - self.ext_conns['redis_cache'].close() - await self.ext_conns['redis_cache'].wait_closed() + self.ext_conns["redis_cache"].close() + await self.ext_conns["redis_cache"].wait_closed() await super().close() diff --git a/seraphsix/cogs/clan.py b/seraphsix/cogs/clan.py index 8ac2241..d2c0602 100644 --- a/seraphsix/cogs/clan.py +++ b/seraphsix/cogs/clan.py @@ -10,7 +10,12 @@ from tortoise.query_utils import Q from seraphsix import constants -from seraphsix.cogs.utils.checks import is_clan_admin, is_valid_game_mode, is_registered, clan_is_linked +from seraphsix.cogs.utils.checks import ( + is_clan_admin, + is_valid_game_mode, + is_registered, + clan_is_linked, +) from seraphsix.cogs.utils.helpers import date_as_string, get_requestor from seraphsix.cogs.utils.message_manager import MessageManager from seraphsix.cogs.utils.paginator import FieldPages, EmbedPages @@ -18,11 +23,19 @@ from seraphsix.models import deserializer, serializer from seraphsix.models.database import Clan, Guild, ClanMemberApplication, Role from seraphsix.models.destiny import ( - DestinyMembershipResponse, DestinyMemberGroupResponse, DestinyGroupResponse, DestinyGroupPendingMembersResponse, - DestinySearchPlayerResponse + DestinyMembershipResponse, + DestinyMemberGroupResponse, + DestinyGroupResponse, + DestinyGroupPendingMembersResponse, + DestinySearchPlayerResponse, ) from seraphsix.tasks.activity import get_game_counts, get_last_active -from seraphsix.tasks.core import execute_pydest, get_primary_membership, execute_pydest_auth, get_memberships +from seraphsix.tasks.core import ( + execute_pydest, + get_primary_membership, + execute_pydest_auth, + get_memberships, +) from seraphsix.tasks.clan import info_sync, member_sync log = logging.getLogger(__name__) @@ -36,30 +49,33 @@ async def get_admin_group(self, ctx): clan_db = await Clan.get( guild__guild_id=ctx.message.guild.id, members__member_type__gte=constants.CLAN_MEMBER_ADMIN, - members__member__discord_id=ctx.author.id + members__member__discord_id=ctx.author.id, ) if not clan_db: raise InvalidAdminError return clan_db async def get_user_details(self, args): - username, platform_id = (None,)*2 + username, platform_id = (None,) * 2 if not args: raise InvalidCommandError("Username is required") - if args[-1] == '-platform': + if args[-1] == "-platform": raise InvalidCommandError( - f"Platform must be specified like `-platform {list(constants.PLATFORM_EMOJI_MAP.keys())}`") + f"Platform must be specified like `-platform {list(constants.PLATFORM_EMOJI_MAP.keys())}`" + ) - if '-platform' in args: + if "-platform" in args: platform_name = args[-1].lower() - username = ' '.join(args[0:-2]) + username = " ".join(args[0:-2]) platform_id = constants.PLATFORM_MAP.get(platform_name) if not platform_id: - raise InvalidCommandError(f"Invalid platform `{platform_name}` was specified") + raise InvalidCommandError( + f"Invalid platform `{platform_name}` was specified" + ) else: - username = ' '.join(args) + username = " ".join(args) if not username: raise InvalidCommandError("Username is required") @@ -68,11 +84,17 @@ async def get_user_details(self, args): async def get_member_db(self, ctx, username, include_clan=False): try: - member_discord = await commands.MemberConverter().convert(ctx, str(username)) + member_discord = await commands.MemberConverter().convert( + ctx, str(username) + ) except BadArgument: - member_query = self.bot.database.get_member_by_naive_username(username, include_clan=include_clan) + member_query = self.bot.database.get_member_by_naive_username( + username, include_clan=include_clan + ) else: - member_query = self.bot.database.get_member_by_discord_id(member_discord.id, include_clan=include_clan) + member_query = self.bot.database.get_member_by_discord_id( + member_discord.id, include_clan=include_clan + ) return await asyncio.create_task(member_query) async def get_bungie_details(self, username, bungie_id=None, platform_id=None): @@ -82,8 +104,9 @@ async def get_bungie_details(self, username, bungie_id=None, platform_id=None): if bungie_id: try: player = await execute_pydest( - self.bot.destiny.api.get_membership_data_by_id, bungie_id, - return_type=DestinyMembershipResponse + self.bot.destiny.api.get_membership_data_by_id, + bungie_id, + return_type=DestinyMembershipResponse, ) except pydest.PydestException as e: log_message = f"Could not find Destiny player for {username}" @@ -97,8 +120,10 @@ async def get_bungie_details(self, username, bungie_id=None, platform_id=None): break else: player = await execute_pydest( - self.bot.destiny.api.search_destiny_player, platform_id, username, - return_type=DestinySearchPlayerResponse + self.bot.destiny.api.search_destiny_player, + platform_id, + username, + return_type=DestinySearchPlayerResponse, ) if not player.response: log_message = f"Could not find Destiny player for {username}" @@ -107,14 +132,18 @@ async def get_bungie_details(self, username, bungie_id=None, platform_id=None): if len(player.response) == 1: membership = player.response[0] - if membership.display_name.lower() == username_lower and membership.membership_type == platform_id: + if ( + membership.display_name.lower() == username_lower + and membership.membership_type == platform_id + ): membership_id = membership.membership_id platform_id = membership.membership_type else: membership_orig = membership profile = await execute_pydest( - self.bot.destiny.api.get_membership_data_by_id, membership.membership_id, - return_type=DestinyMembershipResponse + self.bot.destiny.api.get_membership_data_by_id, + membership.membership_id, + return_type=DestinyMembershipResponse, ) for membership in profile.response.destiny_memberships: if membership.display_name.lower() == username_lower: @@ -127,14 +156,17 @@ async def get_bungie_details(self, username, bungie_id=None, platform_id=None): for membership in player.response: display_name = membership.display_name.lower() membership_type = membership.membership_type - if membership_type == platform_id and display_name == username_lower: + if ( + membership_type == platform_id + and display_name == username_lower + ): membership_id = membership.membership_id platform_id = membership.membership_type break return membership_id, platform_id async def create_application_embed(self, ctx, requestor_db, guild_db): - redis_cache = self.bot.ext_conns['redis_cache'] + redis_cache = self.bot.ext_conns["redis_cache"] if requestor_db.bungie_username: membership_name = requestor_db.bungie_username @@ -144,8 +176,10 @@ async def create_application_embed(self, ctx, requestor_db, guild_db): group_id = None group_name = None groups_info = await execute_pydest( - self.bot.destiny.api.get_groups_for_member, platform_id, membership_id, - return_type=DestinyMemberGroupResponse + self.bot.destiny.api.get_groups_for_member, + platform_id, + membership_id, + return_type=DestinyMemberGroupResponse, ) if len(groups_info.response.results) > 0: for group in groups_info.response.results: @@ -154,23 +188,27 @@ async def create_application_embed(self, ctx, requestor_db, guild_db): group_name = group.group.name if group_id and group_name: - group_url = f'https://www.bungie.net/en/ClanV2/Index?groupId={group_id}' - group_link = f'[{group_name}]({group_url})' + group_url = f"https://www.bungie.net/en/ClanV2/Index?groupId={group_id}" + group_link = f"[{group_name}]({group_url})" else: - group_link = 'None' + group_link = "None" - last_active = await get_last_active(self.bot.ext_conns, platform_id=platform_id, member_id=membership_id) + last_active = await get_last_active( + self.bot.ext_conns, platform_id=platform_id, member_id=membership_id + ) embed = discord.Embed( colour=constants.BLUE, - title=f"Clan Application for {ctx.author.display_name}" + title=f"Clan Application for {ctx.author.display_name}", ) bungie_url = f"https://www.bungie.net/en/Profile/{platform_id}/{membership_id}" bungie_link = f"[{membership_name}]({bungie_url})" if requestor_db.discord_id: - member_discord = await commands.MemberConverter().convert(ctx, str(requestor_db.discord_id)) + member_discord = await commands.MemberConverter().convert( + ctx, str(requestor_db.discord_id) + ) discord_username = str(member_discord) embed.add_field(name="Last Active Date", value=date_as_string(last_active)) @@ -185,29 +223,33 @@ async def create_application_embed(self, ctx, requestor_db, guild_db): embed.set_thumbnail(url=str(ctx.author.avatar_url)) await redis_cache.set( - f'{ctx.guild.id}-clan-application-{requestor_db.id}', serializer(embed)) + f"{ctx.guild.id}-clan-application-{requestor_db.id}", serializer(embed) + ) return embed async def get_inactive_members(self, ctx, clan_db): inactive_members_filtered = [] - query = Role.filter( - guild__guild_id=ctx.guild.id, - is_protected_clanmember=True - ) + query = Role.filter(guild__guild_id=ctx.guild.id, is_protected_clanmember=True) protected_roles = [role.role_id for role in await query] inactive_members = await self.bot.database.get_clan_members_inactive(clan_db) if len(inactive_members) > 0: for clanmember in inactive_members: - time_delta = (datetime.now(pytz.utc) - clanmember.last_active).total_seconds() - platform_id, membership_id, username = get_primary_membership(clanmember.member) + time_delta = ( + datetime.now(pytz.utc) - clanmember.last_active + ).total_seconds() + platform_id, membership_id, username = get_primary_membership( + clanmember.member + ) member_discord = ctx.guild.get_member(clanmember.member.discord_id) if member_discord: member_roles = [role.id for role in member_discord.roles] if any(role in protected_roles for role in member_roles): continue - inactive_members_filtered.append((time_delta, platform_id, membership_id, username)) + inactive_members_filtered.append( + (time_delta, platform_id, membership_id, username) + ) inactive_members_filtered.sort(reverse=True) return inactive_members_filtered @@ -242,21 +284,26 @@ async def link(self, ctx, group_id): manager = MessageManager(ctx) if not group_id: - return await manager.send_and_clean("Command must include the the100 group ID") + return await manager.send_and_clean( + "Command must include the the100 group ID" + ) res = await self.bot.the100.get_group(group_id) - if res.get('error'): - return await manager.send_and_clean(f"Could not locate the100 group {group_id}") + if res.get("error"): + return await manager.send_and_clean( + f"Could not locate the100 group {group_id}" + ) - group_name = res['name'] - callsign = res['clan_tag'] + group_name = res["name"] + callsign = res["clan_tag"] clan_db = await self.get_admin_group(ctx) if clan_db.the100_group_id: return await manager.send_and_clean( - f"**{clan_db.name} [{clan_db.callsign}]** is already linked to another the100 group.") + f"**{clan_db.name} [{clan_db.callsign}]** is already linked to another the100 group." + ) - clan_db.the100_group_id = res['id'] + clan_db.the100_group_id = res["id"] await clan_db.save() message = ( @@ -276,14 +323,17 @@ async def unlink(self, ctx, group_id): clan_db = await self.get_admin_group(ctx) if not clan_db.the100_group_id: return await manager.send_and_clean( - f"**{clan_db.name} [{clan_db.callsign}]** is not linked to a the100 group.") + f"**{clan_db.name} [{clan_db.callsign}]** is not linked to a the100 group." + ) res = await self.bot.the100.get_group(clan_db.the100_group_id) - if res.get('error'): - return await manager.send_and_clean(f"Could not locate the100 group {clan_db.the100_group_id}") + if res.get("error"): + return await manager.send_and_clean( + f"Could not locate the100 group {clan_db.the100_group_id}" + ) - group_name = res['name'] - callsign = res['clan_tag'] + group_name = res["name"] + callsign = res["clan_tag"] clan_db.the100_group_id = None await clan_db.save() @@ -299,61 +349,71 @@ async def unlink(self, ctx, group_id): @commands.guild_only() async def info(self, ctx, *args): """Show information for all connected clans""" - redis_cache = self.bot.ext_conns['redis_cache'] + redis_cache = self.bot.ext_conns["redis_cache"] manager = MessageManager(ctx) clan_dbs = await self.bot.database.get_clans_by_guild(ctx.guild.id) if not clan_dbs: - return await manager.send_and_clean("No connected clans found", mention=False) + return await manager.send_and_clean( + "No connected clans found", mention=False + ) embeds = [] - clan_redis_key = f'{ctx.guild.id}-clan-info' + clan_redis_key = f"{ctx.guild.id}-clan-info" clan_info_redis = await redis_cache.get(clan_redis_key) - if clan_info_redis and '-nocache' not in args: - log.debug(f'{clan_redis_key} {clan_info_redis}') + if clan_info_redis and "-nocache" not in args: + log.debug(f"{clan_redis_key} {clan_info_redis}") await redis_cache.expire(clan_redis_key, constants.TIME_HOUR_SECONDS) - embeds = [discord.Embed.from_dict(embed) for embed in deserializer(clan_info_redis)] + embeds = [ + discord.Embed.from_dict(embed) + for embed in deserializer(clan_info_redis) + ] else: for clan_db in clan_dbs: group = await execute_pydest( - self.bot.destiny.api.get_group, clan_db.clan_id, return_type=DestinyGroupResponse) + self.bot.destiny.api.get_group, + clan_db.clan_id, + return_type=DestinyGroupResponse, + ) if not group.response: log.error( f"Could not get details for clan {clan_db.name} ({clan_db.clan_id}) - " f"{group.error_status} {group.error_description}" ) - return await manager.send_and_clean(f"Clan {clan_db.name} not found", mention=False) + return await manager.send_and_clean( + f"Clan {clan_db.name} not found", mention=False + ) else: group = group.response embed = discord.Embed( colour=constants.BLUE, title=group.detail.motto, - description=group.detail.about + description=group.detail.about, ) embed.set_author( name=f"{group.detail.name} [{group.detail.clan_info.clan_callsign}]", - url=f"https://www.bungie.net/en/ClanV2?groupid={clan_db.clan_id}" + url=f"https://www.bungie.net/en/ClanV2?groupid={clan_db.clan_id}", ) embed.add_field( - name="Members", - value=group.detail.member_count, - inline=True + name="Members", value=group.detail.member_count, inline=True ) embed.add_field( name="Founder", value=group.founder.bungie_net_user_info.display_name, - inline=True + inline=True, ) embed.add_field( name="Founded", value=date_as_string(group.detail.creation_date), - inline=True + inline=True, ) embeds.append(embed) - await redis_cache.set(clan_redis_key, serializer( - [embed.to_dict() for embed in embeds]), expire=constants.TIME_HOUR_SECONDS + await redis_cache.set( + clan_redis_key, + serializer([embed.to_dict() for embed in embeds]), + expire=constants.TIME_HOUR_SECONDS, ) if len(embeds) > 1: @@ -385,7 +445,9 @@ async def roster(self, ctx, *args): # await self.bot.ext_conns['redis_cache'].rpush(f"{ctx.guild.id}-clan-roster", serializer(member)) # await self.bot.ext_conns['redis_cache'].expire(f"{ctx.guild.id}-clan-roster", constants.TIME_HOUR_SECONDS) # members = members_db - members_db = await self.bot.database.get_clan_members([clan_db.clan_id for clan_db in clan_dbs]) + members_db = await self.bot.database.get_clan_members( + [clan_db.clan_id for clan_db in clan_dbs] + ) platform_names = list(constants.PLATFORM_MAP.keys()) platform_ids = list(constants.PLATFORM_MAP.values()) @@ -406,7 +468,7 @@ async def roster(self, ctx, *args): if member.bungie_username: username = member.bungie_username else: - username = f'{member.xbox_username} X' + username = f"{member.xbox_username} X" memberships = get_memberships(member) if constants.PLATFORM_BUNGIE in memberships.keys(): username = memberships[constants.PLATFORM_BUNGIE][1] @@ -414,7 +476,9 @@ async def roster(self, ctx, *args): username = list(memberships.values())[0][1] platform_emojis = [ - constants.PLATFORM_EMOJI_MAP.get(platform_names[platform_ids.index(platform)]) + constants.PLATFORM_EMOJI_MAP.get( + platform_names[platform_ids.index(platform)] + ) for platform in memberships.keys() ] @@ -422,7 +486,7 @@ async def roster(self, ctx, *args): f"{username} {' '.join([str(self.bot.get_emoji(emoji)) for emoji in platform_emojis if emoji])}", f"Clan: {clanmember.clan.name} [{clanmember.clan.callsign}]\n" f"Join Date: {date_as_string(clanmember.join_date)}\n" - f"Timezone: {timezone}" + f"Timezone: {timezone}", ) clans[clanmember.clan.id][username] = member_info @@ -431,10 +495,11 @@ async def roster(self, ctx, *args): for v in sorted(clans[i].keys()): entries.append(clans[i][v]) p = FieldPages( - ctx, entries=entries, + ctx, + entries=entries, per_page=5, title="Roster for All Connected Clans", - color=constants.BLUE + color=constants.BLUE, ) await p.paginate() @@ -443,7 +508,9 @@ async def pending(self, ctx): """Show a list of pending members (Admin only, requires registration)""" manager = MessageManager(ctx) - admin_db = await self.bot.database.get_member_by_discord_id(ctx.author.id, include_clan=False) + admin_db = await self.bot.database.get_member_by_discord_id( + ctx.author.id, include_clan=False + ) clan_db = await self.get_admin_group(ctx) members = await execute_pydest_auth( @@ -453,12 +520,11 @@ async def pending(self, ctx): manager, group_id=clan_db.clan_id, access_token=admin_db.bungie_access_token, - return_type=DestinyGroupPendingMembersResponse + return_type=DestinyGroupPendingMembersResponse, ) embed = discord.Embed( - colour=constants.BLUE, - title=f"Pending Clan Members in {clan_db.name}" + colour=constants.BLUE, title=f"Pending Clan Members in {clan_db.name}" ) if len(members.response.results) == 0: @@ -488,26 +554,33 @@ async def pending(self, ctx): Examples: ?clan approve username ?clan approve username -platform xbox -""") +""" + ) async def approve(self, ctx, *args): """Approve a pending member (Admin only, requires registration)""" manager = MessageManager(ctx) username, platform_id = await self.get_user_details(args) member_db = await self.get_member_db(ctx, username) - admin_db = await self.bot.database.get_member_by_discord_id(ctx.author.id, include_clan=False) + admin_db = await self.bot.database.get_member_by_discord_id( + ctx.author.id, include_clan=False + ) clan_db = await self.get_admin_group(ctx) if clan_db.platform: platform_id = clan_db.platform elif not platform_id: - raise InvalidCommandError("Platform was not specified and clan default platform is not set") + raise InvalidCommandError( + "Platform was not specified and clan default platform is not set" + ) bungie_id = None if member_db: bungie_id = member_db.bungie_id - membership_id, platform_id = await self.get_bungie_details(username, bungie_id, platform_id) + membership_id, platform_id = await self.get_bungie_details( + username, bungie_id, platform_id + ) res = await execute_pydest_auth( self.bot.ext_conns, @@ -518,10 +591,10 @@ async def approve(self, ctx, *args): membership_type=platform_id, membership_id=membership_id, message=f"Welcome to {clan_db.name}!", - access_token=admin_db.bungie_access_token + access_token=admin_db.bungie_access_token, ) - if res.error_status != 'Success': + if res.error_status != "Success": message = f"Could not approve **{username}**" log.info(f"Could not approve '{username}': {res}") else: @@ -534,7 +607,9 @@ async def invited(self, ctx): """Show a list of invited members (Admin only, requires registration)""" manager = MessageManager(ctx) - admin_db = await self.bot.database.get_member_by_discord_id(ctx.author.id, include_clan=False) + admin_db = await self.bot.database.get_member_by_discord_id( + ctx.author.id, include_clan=False + ) clan_db = await self.get_admin_group(ctx) members = await execute_pydest_auth( @@ -544,12 +619,11 @@ async def invited(self, ctx): manager, group_id=clan_db.clan_id, access_token=admin_db.bungie_access_token, - return_type=DestinyGroupPendingMembersResponse + return_type=DestinyGroupPendingMembersResponse, ) embed = discord.Embed( - colour=constants.BLUE, - title=f"Invited Clan Members in {clan_db.name}" + colour=constants.BLUE, title=f"Invited Clan Members in {clan_db.name}" ) if len(members.response.results) == 0: @@ -579,26 +653,33 @@ async def invited(self, ctx): Examples: ?clan invite username ?clan invite username -platform xbox -""") +""" + ) async def invite(self, ctx, *args): """Invite a member by username (Admin only, requires registration)""" manager = MessageManager(ctx) username, platform_id = await self.get_user_details(args) member_db = await self.get_member_db(ctx, username) - admin_db = await self.bot.database.get_member_by_discord_id(ctx.author.id, include_clan=False) + admin_db = await self.bot.database.get_member_by_discord_id( + ctx.author.id, include_clan=False + ) clan_db = await self.get_admin_group(ctx) if not platform_id and clan_db.platform: platform_id = clan_db.platform else: - raise InvalidCommandError("Platform was not specified and clan default platform is not set") + raise InvalidCommandError( + "Platform was not specified and clan default platform is not set" + ) bungie_id = None if member_db: bungie_id = member_db.bungie_id - membership_id, platform_id = await self.get_bungie_details(username, bungie_id, platform_id) + membership_id, platform_id = await self.get_bungie_details( + username, bungie_id, platform_id + ) res = await execute_pydest_auth( self.bot.ext_conns, @@ -609,14 +690,14 @@ async def invite(self, ctx, *args): membership_type=platform_id, membership_id=membership_id, message=f"Join my clan {clan_db.name}!", - access_token=admin_db.bungie_access_token + access_token=admin_db.bungie_access_token, ) - if res.error_status == 'ClanTargetDisallowsInvites': + if res.error_status == "ClanTargetDisallowsInvites": message = f"User **{username}** has disabled clan invites" - elif res.error_status == 'ClanMaximumMembershipReached': + elif res.error_status == "ClanMaximumMembershipReached": message = f"Could not invite **{username}**, clan is full" - elif res.error_status != 'Success': + elif res.error_status != "Success": message = f"Could not invite **{username}**" log.info(f"Could not invite '{username}': {res}") else: @@ -630,7 +711,9 @@ async def sync(self, ctx): """Sync member list with Destiny (Admin only)""" manager = MessageManager(ctx) - member_changes = await member_sync(self.bot.ext_conns, ctx.guild.id, str(ctx.guild)) + member_changes = await member_sync( + self.bot.ext_conns, ctx.guild.id, str(ctx.guild) + ) clan_info_changes = await info_sync(self.bot.ext_conns, ctx.guild.id) clan_dbs = await self.bot.database.get_clans_by_guild(ctx.guild.id) @@ -642,57 +725,52 @@ async def sync(self, ctx): ) added, removed, changed = [ - member_changes[clan_db.clan_id][k] for k in ['added', 'removed', 'changed']] + member_changes[clan_db.clan_id][k] + for k in ["added", "removed", "changed"] + ] - if not added and not removed and not changed \ - and not clan_info_changes.get(clan_db.clan_id): - embed.add_field( - name="No Changes", - value='-' - ) + if ( + not added + and not removed + and not changed + and not clan_info_changes.get(clan_db.clan_id) + ): + embed.add_field(name="No Changes", value="-") if added: if len(added) > 10: members_value = f"Too many to list: {len(added)} total" else: - members_value = ', '.join(added) - embed.add_field( - name="Members Added", - value=members_value, - inline=False - ) + members_value = ", ".join(added) + embed.add_field(name="Members Added", value=members_value, inline=False) if removed: if len(removed) >= 10: members_value = f"Too many to list: {len(removed)} total" else: - members_value = ', '.join(removed) + members_value = ", ".join(removed) embed.add_field( - name="Members Removed", - value=members_value, - inline=False + name="Members Removed", value=members_value, inline=False ) if changed: embed.add_field( - name="Members Changed", - value=', '.join(changed), - inline=False + name="Members Changed", value=", ".join(changed), inline=False ) if clan_info_changes.get(clan_db.clan_id): changes = clan_info_changes[clan_db.clan_id] - if changes.get('name'): + if changes.get("name"): embed.add_field( name="Name Changed", value=f"From **{changes['name']['from']}** to **{changes['name']['to']}**", - inline=False + inline=False, ) - if changes.get('callsign'): + if changes.get("callsign"): embed.add_field( name="Callsign Changed", value=f"From **{changes['callsign']['from']}** to **{changes['callsign']['to']}**", - inline=False + inline=False, ) embeds.append(embed) @@ -712,19 +790,25 @@ async def apply(self, ctx): requestor_db = await get_requestor(ctx) guild_db = await Guild.get_or_none(guild_id=ctx.guild.id) - clan_app_db = await ClanMemberApplication.get_or_none(guild=guild_db, member=requestor_db) + clan_app_db = await ClanMemberApplication.get_or_none( + guild=guild_db, member=requestor_db + ) if not clan_app_db: embed = await self.create_application_embed(ctx, requestor_db, guild_db) - application_embed = await manager.send_embed(embed, channel_id=guild_db.admin_channel) + application_embed = await manager.send_embed( + embed, channel_id=guild_db.admin_channel + ) await ClanMemberApplication.create( guild=guild_db, member=requestor_db, message_id=application_embed.id, - approved=False + approved=False, ) - await manager.send_and_clean("Your application has been submitted for admin approval.") + await manager.send_and_clean( + "Your application has been submitted for admin approval." + ) else: if not clan_app_db.approved: message = "Your application is still pending for admin approval." @@ -734,25 +818,31 @@ async def apply(self, ctx): @admin.command() async def applied(self, ctx): - redis_cache = self.bot.ext_conns['redis_cache'] + redis_cache = self.bot.ext_conns["redis_cache"] manager = MessageManager(ctx) - admin_channel = self.bot.get_channel(self.bot.guild_map[ctx.guild.id].admin_channel) + admin_channel = self.bot.get_channel( + self.bot.guild_map[ctx.guild.id].admin_channel + ) - cursor = b'0' + cursor = b"0" while cursor: - cursor, keys = await redis_cache.scan(cursor, match=f'{ctx.guild.id}-clan-application-*') + cursor, keys = await redis_cache.scan( + cursor, match=f"{ctx.guild.id}-clan-application-*" + ) if len(keys) > 0: for key in keys: embed_packed = await redis_cache.get(key) - member_db_id = key.decode('utf-8').split('-')[-1] + member_db_id = key.decode("utf-8").split("-")[-1] application_db = await ClanMemberApplication.filter( member_id=member_db_id ) previous_message_id = application_db.message_id - previous_message = await admin_channel.fetch_message(previous_message_id) + previous_message = await admin_channel.fetch_message( + previous_message_id + ) await previous_message.delete() new_message = await manager.send_embed(deserializer(embed_packed)) @@ -761,9 +851,7 @@ async def applied(self, ctx): else: await manager.send_and_clean("No applications found.") - @clan.command( - usage=f"<{', '.join(constants.SUPPORTED_GAME_MODES.keys())}>" - ) + @clan.command(usage=f"<{', '.join(constants.SUPPORTED_GAME_MODES.keys())}>") @clan_is_linked() @is_valid_game_mode() @commands.guild_only() @@ -777,12 +865,12 @@ async def games(self, ctx, game_mode: str): embed = discord.Embed( colour=constants.BLUE, - title=f"Eligible {game_mode.title().replace('Pvp', 'PvP')} Games for All Members" + title=f"Eligible {game_mode.title().replace('Pvp', 'PvP')} Games for All Members", ) total_count = 0 if len(game_counts) == 1: - total_count, = game_counts.values() + (total_count,) = game_counts.values() else: for game, count in game_counts.items(): embed.add_field(name=game.title(), value=str(count)) @@ -822,36 +910,42 @@ async def update_games(self, ctx, *args): message = f"Queueing task to find recent games for all members of {guild_name} ({guild_id})" log.info(message) - await self.bot.ext_conns['redis_jobs'].enqueue_job( - 'store_all_games', guild_id, guild_name, 30, False, _job_id=f'store_all_games-{guild_id}' + await self.bot.ext_conns["redis_jobs"].enqueue_job( + "store_all_games", + guild_id, + guild_name, + 30, + False, + _job_id=f"store_all_games-{guild_id}", ) await manager.send_message(message, mention=False, clean=False) - @ admin.command() + @admin.command() async def inactive(self, ctx, *args): manager = MessageManager(ctx) embeds = [] - clan_dbs = await Clan.filter(guild__guild_id=ctx.guild.id).prefetch_related('guild') + clan_dbs = await Clan.filter(guild__guild_id=ctx.guild.id).prefetch_related( + "guild" + ) for clan_db in clan_dbs: embed = discord.Embed( colour=constants.BLUE, - title=f"Clan Members in {clan_db.name} Inactive for over 30 Days" + title=f"Clan Members in {clan_db.name} Inactive for over 30 Days", ) inactive_members = await self.get_inactive_members(ctx, clan_db) if len(inactive_members) == 0: - embed.add_field( - name="None", - value='-' - ) + embed.add_field(name="None", value="-") else: for inactive_member in inactive_members: time_delta, _, _, username = inactive_member months, remainder = divmod(time_delta, 2628000) days, _ = divmod(remainder, 86400) - embed.add_field(name=username, value=f'{int(months)} months {int(days)} days') + embed.add_field( + name=username, value=f"{int(months)} months {int(days)} days" + ) embeds.append(embed) @@ -861,9 +955,11 @@ async def inactive(self, ctx, *args): @admin.command() async def kick(self, ctx, *args): manager = MessageManager(ctx) - username = ' '.join(args) + username = " ".join(args) - admin_db = await self.bot.database.get_member_by_discord_id(ctx.author.id, include_clan=True) + admin_db = await self.bot.database.get_member_by_discord_id( + ctx.author.id, include_clan=True + ) member_db = await self.get_member_db(ctx, username, include_clan=True) if not member_db: @@ -871,15 +967,10 @@ async def kick(self, ctx, *args): platform_id, membership_id, username = get_primary_membership(member_db) - confirm = { - constants.EMOJI_CHECKMARK: True, - constants.EMOJI_CROSSMARK: False - } + confirm = {constants.EMOJI_CHECKMARK: True, constants.EMOJI_CROSSMARK: False} confirm_res = await manager.send_message_react( - f"Kick **{username}**?", - reactions=confirm.keys(), - clean=False + f"Kick **{username}**?", reactions=confirm.keys(), clean=False ) if confirm_res == constants.EMOJI_CROSSMARK: return await manager.send_and_clean("Canceling command") @@ -892,13 +983,14 @@ async def kick(self, ctx, *args): group_id=admin_db.clan.clan_id, membership_type=platform_id, membership_id=membership_id, - access_token=admin_db.member.bungie_access_token + access_token=admin_db.member.bungie_access_token, ) await member_db.delete() # TODO return await manager.send_message( - f"Member **{username}** has been kicked from {admin_db.clan.name}") + f"Member **{username}** has been kicked from {admin_db.clan.name}" + ) @admin.command() async def purge(self, ctx, *args): @@ -909,23 +1001,29 @@ async def purge(self, ctx, *args): inactive_members = await self.get_inactive_members(ctx, admin_db.clan) if not inactive_members: return await manager.send_message( - f"Clan {admin_db.clan.name} has no inactive members in the past 30 days") + f"Clan {admin_db.clan.name} has no inactive members in the past 30 days" + ) roles_db = await Role.filter( Q( Q(guild__guild_id=ctx.guild.id), Q( - Q(is_clanmember=True), Q(is_new_clanmember=True), Q(is_non_clanmember=True), join_type="OR" - ) + Q(is_clanmember=True), + Q(is_new_clanmember=True), + Q(is_non_clanmember=True), + join_type="OR", + ), ) ) member_roles = [ - ctx.guild.get_role(role_db.role_id) for role_db in roles_db + ctx.guild.get_role(role_db.role_id) + for role_db in roles_db if role_db.is_clanmember or role_db.is_new_clanmember ] non_member_roles = [ - ctx.guild.get_role(role_db.role_id) for role_db in roles_db + ctx.guild.get_role(role_db.role_id) + for role_db in roles_db if role_db.is_non_clanmember ] @@ -945,27 +1043,20 @@ async def purge(self, ctx, *args): if member_db.member.discord_id: member_discord = ctx.guild.get_member(member_db.member.discord_id) if member_discord: - kick_message += ( - f"(Discord: {member_discord.display_name}), " - ) + kick_message += f"(Discord: {member_discord.display_name}), " if not member_discord: - kick_message += ( - "(not in this Discord server or not registered), " - ) + kick_message += "(not in this Discord server or not registered), " kick_message += f"inactive for {int(months)} months {int(days)} days?" confirm = { constants.EMOJI_CHECKMARK: True, - constants.EMOJI_CROSSMARK: False + constants.EMOJI_CROSSMARK: False, } confirm_res = await manager.send_message_react( - kick_message, - reactions=confirm.keys(), - clean=False, - with_cancel=True + kick_message, reactions=confirm.keys(), clean=False, with_cancel=True ) if confirm_res == constants.EMOJI_CROSSMARK: continue @@ -980,7 +1071,7 @@ async def purge(self, ctx, *args): group_id=admin_db.clan.clan_id, membership_type=platform_id, membership_id=membership_id, - access_token=admin_db.bungie_access_token + access_token=admin_db.bungie_access_token, ) await member_db.delete() # TODO @@ -989,7 +1080,9 @@ async def purge(self, ctx, *args): f"has been kicked from {admin_db.clan.name} after being inactive " f"for {int(months)} months {int(days)} days" ) - await manager.send_message(f"Member **{username}** {announcement_base}", mention=False, clean=False) + await manager.send_message( + f"Member **{username}** {announcement_base}", mention=False, clean=False + ) if member_discord: await member_discord.remove_roles(*member_roles) @@ -1003,9 +1096,11 @@ async def purge(self, ctx, *args): if announcements: # await set_cached_members(self.bot.ext_conns, ctx.guild.id, ctx.guild.name) - announcement_channel = ctx.guild.get_channel(self.bot.guild_map[ctx.guild.id].announcement_channel) + announcement_channel = ctx.guild.get_channel( + self.bot.guild_map[ctx.guild.id].announcement_channel + ) if announcement_channel: - await announcement_channel.send('\n'.join(announcements)) + await announcement_channel.send("\n".join(announcements)) def setup(bot): diff --git a/seraphsix/cogs/game.py b/seraphsix/cogs/game.py index 964082b..bce3995 100644 --- a/seraphsix/cogs/game.py +++ b/seraphsix/cogs/game.py @@ -44,7 +44,7 @@ async def list(self, ctx): games = [] for result in results: - if isinstance(result, dict) and result.get('error'): + if isinstance(result, dict) and result.get("error"): log.error(result) continue games.extend(result) @@ -55,44 +55,40 @@ async def list(self, ctx): embeds = [] for game in games: try: - spots_reserved = game['party_size'] - 1 + spots_reserved = game["party_size"] - 1 except TypeError: continue - start_time = datetime.fromisoformat(game['start_time']).astimezone(tz=pytz.utc) + start_time = datetime.fromisoformat(game["start_time"]).astimezone( + tz=pytz.utc + ) embed = discord.Embed( color=constants.BLUE, ) - embed.set_thumbnail( - url=(constants.THE100_LOGO_URL) - ) + embed.set_thumbnail(url=(constants.THE100_LOGO_URL)) embed.add_field( name="Activity", - value=f"[{game['category']}](https://www.the100.io/gaming_sessions/{game['id']})" + value=f"[{game['category']}](https://www.the100.io/gaming_sessions/{game['id']})", ) embed.add_field( name="Start Time", - value=start_time.strftime(constants.THE100_DATE_DISPLAY) - ) - embed.add_field( - name="Description", - value=game['name'], - inline=False + value=start_time.strftime(constants.THE100_DATE_DISPLAY), ) + embed.add_field(name="Description", value=game["name"], inline=False) primary = [] reserve = [] - for session in game['confirmed_sessions']: - gamertag = session['user']['gamertag'] + for session in game["confirmed_sessions"]: + gamertag = session["user"]["gamertag"] member_db = await ClanMember.get_or_none( - member__the100_id=session['user_id'], - clan__guild__guild_id=ctx.guild.id + member__the100_id=session["user_id"], + clan__guild__guild_id=ctx.guild.id, ) if member_db: gamertag = f"{gamertag} (m)" - if session['reserve_spot']: + if session["reserve_spot"]: reserve.append(gamertag) else: primary.append(gamertag) @@ -102,13 +98,11 @@ async def list(self, ctx): f"Players Joined: {game['primary_users_count']}/{game['team_size']} " f"(Spots Reserved: {spots_reserved})" ), - value=', '.join(primary), - inline=False + value=", ".join(primary), + inline=False, ) embed.add_field( - name="Reserves", - value=', '.join(reserve) or "None", - inline=False + name="Reserves", value=", ".join(reserve) or "None", inline=False ) embed.set_footer( text=( @@ -134,19 +128,20 @@ async def create(self, ctx): base_embed = discord.Embed( color=constants.BLUE, ) - base_embed.set_thumbnail( - url=(constants.THE100_LOGO_URL) - ) - for field in ["Status", "Activity", "Start Time", "Description", "Platform", "Group Only"]: - kwargs = dict( - name=field, - value="Not Set", - inline=True - ) + base_embed.set_thumbnail(url=(constants.THE100_LOGO_URL)) + for field in [ + "Status", + "Activity", + "Start Time", + "Description", + "Platform", + "Group Only", + ]: + kwargs = dict(name=field, value="Not Set", inline=True) if field in ["Description', 'Status"]: - kwargs['inline'] = False + kwargs["inline"] = False if field == "Status": - kwargs['value'] = '**Game Creation In Progress...**' + kwargs["value"] = "**Game Creation In Progress...**" base_embed.add_field(**kwargs) game_embed = await manager.send_embed(base_embed, clean=True) @@ -154,7 +149,9 @@ async def create(self, ctx): # TODO: Figure out how to sanitize the Destiny 1 game activity list game_name = "Destiny 2" game = await self.bot.the100.get_game_by_name(game_name) - game_activities, game_activities_by_id = collate_the100_activities(game['game_activities'], game_name) + game_activities, game_activities_by_id = collate_the100_activities( + game["game_activities"], game_name + ) activity_id = None while not activity_id: @@ -164,7 +161,9 @@ async def create(self, ctx): embed = discord.Embed( color=constants.BLUE, - description='\n'.join([f"{react} - {activity}" for react, activity in reacts.items()]), + description="\n".join( + [f"{react} - {activity}" for react, activity in reacts.items()] + ), ) react = await manager.send_message_react( @@ -172,7 +171,7 @@ async def create(self, ctx): reactions=reacts.keys(), embed=embed, clean=False, - with_cancel=True + with_cancel=True, ) if not react: @@ -184,25 +183,36 @@ async def create(self, ctx): else: game_activities = activity_react - base_embed.set_field_at(1, name="Activity", value=game_activities_by_id[activity_id]) + base_embed.set_field_at( + 1, name="Activity", value=game_activities_by_id[activity_id] + ) await game_embed.edit(embed=base_embed) time = await manager.send_and_get_response( - "Enter time in the format `6/13 10:00pm` (enter `cancel` to cancel post)") - if time.lower() == 'cancel': + "Enter time in the format `6/13 10:00pm` (enter `cancel` to cancel post)" + ) + if time.lower() == "cancel": return await manager.send_and_clean("Canceling post") member_db = await Member.get(discord_id=ctx.author.id) - time_format = datetime.strptime(time, constants.THE100_DATE_CREATE).replace( - year=datetime.now().year).astimezone(tz=pytz.timezone(member_db.timezone)) + time_format = ( + datetime.strptime(time, constants.THE100_DATE_CREATE) + .replace(year=datetime.now().year) + .astimezone(tz=pytz.timezone(member_db.timezone)) + ) - base_embed.set_field_at(2, name="Start Time", - value=time_format.strftime(constants.THE100_DATE_DISPLAY)) + base_embed.set_field_at( + 2, + name="Start Time", + value=time_format.strftime(constants.THE100_DATE_DISPLAY), + ) await game_embed.edit(embed=base_embed) - description = await manager.send_and_get_response("Enter a description (enter `cancel` to cancel post)") - if description.lower() == 'cancel': + description = await manager.send_and_get_response( + "Enter a description (enter `cancel` to cancel post)" + ) + if description.lower() == "cancel": return await manager.send_and_clean("Canceling post") base_embed.set_field_at(3, name="Description", value=description, inline=False) @@ -215,7 +225,7 @@ async def create(self, ctx): "Which platform?", reactions=constants.PLATFORM_EMOJI_MAP.values(), clean=False, - with_cancel=True + with_cancel=True, ) if not platform_react: @@ -223,19 +233,15 @@ async def create(self, ctx): platform = platform_names[platform_emoji_ids.index(platform_react)] - base_embed.set_field_at(4, name="Platform", value=self.bot.get_emoji(platform_react.id)) + base_embed.set_field_at( + 4, name="Platform", value=self.bot.get_emoji(platform_react.id) + ) await game_embed.edit(embed=base_embed) - group_only = { - constants.EMOJI_CHECKMARK: 'group', - constants.EMOJI_CROSSMARK: '' - } + group_only = {constants.EMOJI_CHECKMARK: "group", constants.EMOJI_CROSSMARK: ""} group = await manager.send_message_react( - "Group only?", - reactions=group_only.keys(), - clean=False, - with_cancel=True + "Group only?", reactions=group_only.keys(), clean=False, with_cancel=True ) if not group: @@ -245,44 +251,50 @@ async def create(self, ctx): await game_embed.edit(embed=base_embed) base_embed.set_field_at( - 0, name="Status", value="**Ready to post, please confirm details...**", inline=False) + 0, + name="Status", + value="**Ready to post, please confirm details...**", + inline=False, + ) await game_embed.edit(embed=base_embed) - confirm = { - constants.EMOJI_CHECKMARK: True, - constants.EMOJI_CROSSMARK: False - } + confirm = {constants.EMOJI_CHECKMARK: True, constants.EMOJI_CROSSMARK: False} confirm_res = await manager.send_message_react( - "Create game?", - reactions=confirm.keys(), - clean=False + "Create game?", reactions=confirm.keys(), clean=False ) if confirm_res == constants.EMOJI_CROSSMARK: return await manager.send_and_clean("Canceling post") - message = ' '.join([ - group_only[group], platform, game_activities_by_id[activity_id], - time_format.strftime('%Y-%m-%dT%H:%M:%S%z'), f"\'{description}\'" - ]) + message = " ".join( + [ + group_only[group], + platform, + game_activities_by_id[activity_id], + time_format.strftime("%Y-%m-%dT%H:%M:%S%z"), + f"'{description}'", + ] + ) data = { - 'guild_id': ctx.guild.id, - 'username': ctx.author.name, - 'discriminator': ctx.author.discriminator, - 'message': message + "guild_id": ctx.guild.id, + "username": ctx.author.name, + "discriminator": ctx.author.discriminator, + "message": message, } response = await self.bot.the100.create_gaming_session_discord(data) - response_msg = response['notice'] + response_msg = response["notice"] if "Gaming Session Created!" not in response_msg: await manager.send_message(response_msg) else: - msg_parts = response_msg.strip().split(' ') - msg = ' '.join(msg_parts[0:3]) + msg_parts = response_msg.strip().split(" ") + msg = " ".join(msg_parts[0:3]) link = msg_parts[-1] - base_embed.set_field_at(0, name="Status", value=f"**[{msg}]({link})**", inline=False) + base_embed.set_field_at( + 0, name="Status", value=f"**[{msg}]({link})**", inline=False + ) await game_embed.edit(embed=base_embed) manager.remove_message_from_clean(game_embed) return await manager.clean_messages() diff --git a/seraphsix/cogs/member.py b/seraphsix/cogs/member.py index ad6c439..f45c3f1 100644 --- a/seraphsix/cogs/member.py +++ b/seraphsix/cogs/member.py @@ -8,18 +8,29 @@ from urllib.parse import quote from seraphsix import constants -from seraphsix.cogs.utils.checks import is_valid_game_mode, clan_is_linked, is_registered -from seraphsix.cogs.utils.helpers import get_timezone_name, date_as_string, get_requestor +from seraphsix.cogs.utils.checks import ( + is_valid_game_mode, + clan_is_linked, + is_registered, +) +from seraphsix.cogs.utils.helpers import ( + get_timezone_name, + date_as_string, + get_requestor, +) from seraphsix.cogs.utils.message_manager import MessageManager from seraphsix.models.database import Member from seraphsix.models.destiny import User as DestinyUser, DestinyMembershipResponse -from seraphsix.tasks.activity import get_game_counts, get_sherpa_time_played, execute_pydest +from seraphsix.tasks.activity import ( + get_game_counts, + get_sherpa_time_played, + execute_pydest, +) log = logging.getLogger(__name__) class MemberCog(commands.Cog, name="Member"): - def __init__(self, bot): self.bot = bot @@ -35,7 +46,7 @@ async def member(self, ctx): async def info(self, ctx, *args): """Show member information""" manager = MessageManager(ctx) - member_name = ' '.join(args) + member_name = " ".join(args) requestor_db = await get_requestor(ctx, include_clan=True) if not member_name: @@ -45,28 +56,37 @@ async def info(self, ctx, *args): discord_username = str(ctx.message.author) else: try: - member_discord = await commands.MemberConverter().convert(ctx, str(member_name)) + member_discord = await commands.MemberConverter().convert( + ctx, str(member_name) + ) except BadArgument: discord_username = None else: discord_username = str(member_discord) - clanmember_db = await self.bot.database.get_member_by_naive_username(member_name) + clanmember_db = await self.bot.database.get_member_by_naive_username( + member_name + ) if not clanmember_db: - return await manager.send_and_clean(f"Could not find username `{member_name}` in any connected clans") + return await manager.send_and_clean( + f"Could not find username `{member_name}` in any connected clans" + ) member_db = clanmember_db.member the100_link = None if member_db.the100_username: - the100_url = f"https://www.the100.io/users/{quote(member_db.the100_username)}" + the100_url = ( + f"https://www.the100.io/users/{quote(member_db.the100_username)}" + ) the100_link = f"[{member_db.the100_username}]({the100_url})" bungie_link = None if member_db.bungie_id: bungie_info = await execute_pydest( - self.bot.destiny.api.get_membership_data_by_id, member_db.bungie_id, - return_type=DestinyMembershipResponse + self.bot.destiny.api.get_membership_data_by_id, + member_db.bungie_id, + return_type=DestinyMembershipResponse, ) if not bungie_info.response: bungie_link = member_db.bungie_username @@ -84,7 +104,9 @@ async def info(self, ctx, *args): timezone = f"{tz.strftime('UTC%z')} ({tz.tzname()})" if member_db.discord_id: - member_discord = await commands.MemberConverter().convert(ctx, str(member_db.discord_id)) + member_discord = await commands.MemberConverter().convert( + ctx, str(member_db.discord_id) + ) discord_username = str(member_discord) requestor_is_admin = False @@ -96,22 +118,17 @@ async def info(self, ctx, *args): member_is_admin = True embed = discord.Embed( - colour=constants.BLUE, - title=f"Member Info for {member_name}" + colour=constants.BLUE, title=f"Member Info for {member_name}" ) embed.add_field( name="Clan", - value=f"{clanmember_db.clan.name} [{clanmember_db.clan.callsign}]" - ) - embed.add_field( - name="Join Date", - value=date_as_string(clanmember_db.join_date) + value=f"{clanmember_db.clan.name} [{clanmember_db.clan.callsign}]", ) + embed.add_field(name="Join Date", value=date_as_string(clanmember_db.join_date)) if requestor_is_admin: embed.add_field( - name="Last Active Date", - value=date_as_string(clanmember_db.last_active) + name="Last Active Date", value=date_as_string(clanmember_db.last_active) ) embed.add_field(name="Time Zone", value=timezone) @@ -124,11 +141,15 @@ async def info(self, ctx, *args): embed.add_field(name="The100 Username", value=the100_link) embed.add_field( name="Is Sherpa", - value=constants.EMOJI_CHECKMARK if clanmember_db.is_sherpa else constants.EMOJI_CROSSMARK + value=constants.EMOJI_CHECKMARK + if clanmember_db.is_sherpa + else constants.EMOJI_CROSSMARK, ) embed.add_field( name="Is Admin", - value=constants.EMOJI_CHECKMARK if member_is_admin else constants.EMOJI_CROSSMARK + value=constants.EMOJI_CHECKMARK + if member_is_admin + else constants.EMOJI_CROSSMARK, ) embed.set_footer(text="All times shown in UTC") await manager.send_embed(embed) @@ -140,25 +161,33 @@ async def link(self, ctx): manager = MessageManager(ctx) username = await manager.send_and_get_response( - "What is the in-game username to link to? (enter `cancel` to cancel command)") - if username.lower() == 'cancel': + "What is the in-game username to link to? (enter `cancel` to cancel command)" + ) + if username.lower() == "cancel": return await manager.send_and_clean("Canceling command") discord_user = await manager.send_and_get_response( - "What is the discord user to link to? (enter `cancel` to cancel command)") - if discord_user.lower() == 'cancel': + "What is the discord user to link to? (enter `cancel` to cancel command)" + ) + if discord_user.lower() == "cancel": return await manager.send_and_clean("Canceling command") try: member_discord = await commands.MemberConverter().convert(ctx, discord_user) except BadArgument: - return await manager.send_and_clean(f"Discord user \"{discord_user}\" not found") + return await manager.send_and_clean( + f'Discord user "{discord_user}" not found' + ) react = await manager.send_message_react( "What is the member's game platform?", - reactions=[constants.EMOJI_STEAM, constants.EMOJI_PSN, constants.EMOJI_XBOX], + reactions=[ + constants.EMOJI_STEAM, + constants.EMOJI_PSN, + constants.EMOJI_XBOX, + ], clean=False, - with_cancel=True + with_cancel=True, ) if not react: @@ -166,26 +195,33 @@ async def link(self, ctx): platform_id = constants.PLATFORM_EMOJI_ID[react.id] - member_db = await self.bot.database.get_member_by_platform_username(username, platform_id) + member_db = await self.bot.database.get_member_by_platform_username( + username, platform_id + ) if not member_db: - return await manager.send_and_clean(f"Username \"{username}\" does not match a valid member") + return await manager.send_and_clean( + f'Username "{username}" does not match a valid member' + ) if member_db.discord_id: - member_discord = await commands.MemberConverter().convert(ctx, str(member_db.discord_id)) + member_discord = await commands.MemberConverter().convert( + ctx, str(member_db.discord_id) + ) return await manager.send_and_clean( - f"Username \"{username}\" already linked to Discord user \"{member_discord.display_name}\"") + f'Username "{username}" already linked to Discord user "{member_discord.display_name}"' + ) member_db.discord_id = member_discord.id try: await member_db.save() except Exception: - message = ( - f"Could not link username \"{username}\" to Discord user \"{member_discord.display_name}\"") + message = f'Could not link username "{username}" to Discord user "{member_discord.display_name}"' log.exception(message) return await manager.send_and_clean(message) return await manager.send_and_clean( - f"Linked username \"{username}\" to Discord user \"{member_discord.display_name}\"") + f'Linked username "{username}" to Discord user "{member_discord.display_name}"' + ) @member.command( help=f""" @@ -195,7 +231,8 @@ async def link(self, ctx): Supported game modes: {', '.join(constants.SUPPORTED_GAME_MODES.keys())} Example: ?member games raid -""") +""" + ) @is_valid_game_mode() async def games(self, ctx, *, command: str): """ @@ -214,26 +251,34 @@ async def games(self, ctx, *, command: str): member_db = await self.bot.database.get_member_by_discord_id(discord_id) if not member_db: return await manager.send_and_clean( - f"User `{ctx.author.display_name}` has not registered or is not a clan member", mention=False) - log.info( - f"Getting {game_mode} games for \"{ctx.author.display_name}\"") + f"User `{ctx.author.display_name}` has not registered or is not a clan member", + mention=False, + ) + log.info(f'Getting {game_mode} games for "{ctx.author.display_name}"') else: - member_db = await self.bot.database.get_member_by_naive_username(member_name) + member_db = await self.bot.database.get_member_by_naive_username( + member_name + ) if not member_db: - return await manager.send_and_clean(f"Invalid member name `{member_name}`", mention=False) + return await manager.send_and_clean( + f"Invalid member name `{member_name}`", mention=False + ) log.info( - f"Getting {game_mode} games by gamertag \"{member_name}\" for \"{ctx.author.display_name}\"") + f'Getting {game_mode} games by gamertag "{member_name}" for "{ctx.author.display_name}"' + ) - game_counts = await get_game_counts(self.bot.database, game_mode, member_db=member_db) + game_counts = await get_game_counts( + self.bot.database, game_mode, member_db=member_db + ) embed = discord.Embed( colour=constants.BLUE, - title=f"Eligible {game_mode.title().replace('Pvp', 'PvP').replace('Pve', 'PvE')} Games for {member_name}" + title=f"Eligible {game_mode.title().replace('Pvp', 'PvP').replace('Pve', 'PvE')} Games for {member_name}", ) total_count = 0 if len(game_counts) == 1: - total_count, = game_counts.values() + (total_count,) = game_counts.values() else: for game, count in game_counts.items(): if count > 0: @@ -248,7 +293,8 @@ async def games(self, ctx, *, command: str): Show total time spent in activities with at least one sherpa member. Example: ?member sherpatime -""") +""" + ) async def sherpatime(self, ctx, *args): """ Show total time spent in activities with at least one sherpa member. @@ -262,23 +308,34 @@ async def sherpatime(self, ctx, *args): member_db = await self.bot.database.get_member_by_discord_id(discord_id) if not member_db: return await manager.send_and_clean( - f"User `{ctx.author.display_name}` has not registered or is not a clan member") - log.info( - f"Getting sherpa time played for \"{ctx.author.display_name}\"") + f"User `{ctx.author.display_name}` has not registered or is not a clan member" + ) + log.info(f'Getting sherpa time played for "{ctx.author.display_name}"') else: - member_db = await self.bot.database.get_member_by_naive_username(member_name) + member_db = await self.bot.database.get_member_by_naive_username( + member_name + ) if not member_db: try: - member_discord = await commands.MemberConverter().convert(ctx, member_name) + member_discord = await commands.MemberConverter().convert( + ctx, member_name + ) except BadArgument: - return await manager.send_and_clean(f"Invalid member name `{member_name}`") + return await manager.send_and_clean( + f"Invalid member name `{member_name}`" + ) else: member_name = member_discord.display_name - member_db = await self.bot.database.get_member_by_discord_id(member_discord.id) + member_db = await self.bot.database.get_member_by_discord_id( + member_discord.id + ) if not member_db: - return await manager.send_and_clean(f"Invalid member name `{member_name}`") + return await manager.send_and_clean( + f"Invalid member name `{member_name}`" + ) log.info( - f"Getting sherpa time played by username \"{member_name}\" for \"{ctx.author}\"") + f'Getting sherpa time played by username "{member_name}" for "{ctx.author}"' + ) time_played, sherpa_ids = await get_sherpa_time_played(member_db) @@ -286,23 +343,24 @@ async def sherpatime(self, ctx, *args): if time_played > 0: for sherpa_id in await sherpa_ids: try: - sherpa_discord = await commands.MemberConverter().convert(ctx, str(sherpa_id)) + sherpa_discord = await commands.MemberConverter().convert( + ctx, str(sherpa_id) + ) except MemberNotFound: sherpa_list.append(str(sherpa_id)) else: - sherpa_list.append(f"{sherpa_discord.name}#{sherpa_discord.discriminator}") + sherpa_list.append( + f"{sherpa_discord.name}#{sherpa_discord.discriminator}" + ) embed = discord.Embed( colour=constants.BLUE, title=f"Total time played with a Sherpa by {member_name}", - description=f"{time_played / 3600:.2f} hours" + description=f"{time_played / 3600:.2f} hours", ) if sherpa_list: - embed.add_field( - name="Sherpas Played With", - value=', '.join(sherpa_list) - ) + embed.add_field(name="Sherpas Played With", value=", ".join(sherpa_list)) await manager.send_embed(embed) @is_registered() @@ -315,7 +373,7 @@ async def settimezone(self, ctx): res = await manager.send_message_react( f"Your current timezone is set to `{member_db.timezone}`, would you like to change it?", reactions=[constants.EMOJI_CHECKMARK, constants.EMOJI_CROSSMARK], - clean=False + clean=False, ) if res == constants.EMOJI_CROSSMARK: @@ -323,22 +381,25 @@ async def settimezone(self, ctx): res = await manager.send_and_get_response( "Enter your timezone name and country code, accepted formats are:\n" - "```EST US\nAmerica/New_York US\n0500 US```") - if res.lower() == 'cancel': + "```EST US\nAmerica/New_York US\n0500 US```" + ) + if res.lower() == "cancel": return await manager.send_and_clean("Canceling command") - timezone, country_code = res.split(' ') + timezone, country_code = res.split(" ") timezones = get_timezone_name(timezone, country_code) if not timezones: - return await manager.send_and_clean(f"No timezone found for `{res}`, canceling") + return await manager.send_and_clean( + f"No timezone found for `{res}`, canceling" + ) elif len(timezones) == 1: timezone = next(iter(timezones)) res = await manager.send_message_react( f"Is the timezone `{timezone}` correct?", reactions=[constants.EMOJI_CHECKMARK, constants.EMOJI_CROSSMARK], - clean=False + clean=False, ) if res == constants.EMOJI_CROSSMARK: @@ -348,8 +409,10 @@ async def settimezone(self, ctx): await member_db.save() else: text = "\n".join(sorted(timezones, key=lambda s: s.lower())) - res = await manager.send_and_get_response(f"Which of these timezones is correct?\n```{text}```") - if res.lower() == 'cancel': + res = await manager.send_and_get_response( + f"Which of these timezones is correct?\n```{text}```" + ) + if res.lower() == "cancel": return await manager.send_and_clean("Canceling command") if res in timezones: diff --git a/seraphsix/cogs/register.py b/seraphsix/cogs/register.py index d9163a6..394a41d 100644 --- a/seraphsix/cogs/register.py +++ b/seraphsix/cogs/register.py @@ -13,7 +13,6 @@ class RegisterCog(commands.Cog, name="Register"): - def __init__(self, bot): self.bot = bot @@ -28,31 +27,41 @@ async def register(self, ctx): """ manager = MessageManager(ctx) - embed, user_info = await register(manager, confirm_message="Initial Registration Complete...") + embed, user_info = await register( + manager, confirm_message="Initial Registration Complete..." + ) if not user_info: - await manager.send_private_message("Oops, something went wrong during registration. Please try again.") + await manager.send_private_message( + "Oops, something went wrong during registration. Please try again." + ) return await manager.clean_messages() - bungie_access_token = user_info.get('access_token') + bungie_access_token = user_info.get("access_token") # Fetch platform specific display names and membership IDs try: user = await execute_pydest( - self.bot.destiny.api.get_membership_current_user, bungie_access_token, - return_type=DestinyMembershipResponse + self.bot.destiny.api.get_membership_current_user, + bungie_access_token, + return_type=DestinyMembershipResponse, ) except Exception as e: log.exception(e) - await manager.send_private_message("I can't seem to connect to Bungie right now. Try again later.") + await manager.send_private_message( + "I can't seem to connect to Bungie right now. Try again later." + ) return await manager.clean_messages() if not user.response: - await manager.send_private_message("Oops, something went wrong during registration. Please try again.") + await manager.send_private_message( + "Oops, something went wrong during registration. Please try again." + ) return await manager.clean_messages() if not self.user_has_connected_accounts(user.response): await manager.send_private_message( - "Oops, you don't have any public accounts attached to your Bungie.net profile.") + "Oops, you don't have any public accounts attached to your Bungie.net profile." + ) return await manager.clean_messages() bungie_user = User(user.response) @@ -63,20 +72,22 @@ async def register(self, ctx): (bungie_user.memberships.steam.id, constants.PLATFORM_STEAM), (bungie_user.memberships.stadia.id, constants.PLATFORM_STADIA), (bungie_user.memberships.blizzard.id, constants.PLATFORM_BLIZZARD), - (bungie_user.memberships.bungie.id, constants.PLATFORM_BUNGIE) + (bungie_user.memberships.bungie.id, constants.PLATFORM_BUNGIE), ] member_db = await self.bot.database.get_member_by_platform( - bungie_user.memberships.bungie.id, constants.PLATFORM_BUNGIE) + bungie_user.memberships.bungie.id, constants.PLATFORM_BUNGIE + ) if not member_db: # Create a list of member id with their respective platforms, if the id is not null - member_id_list = ((member_id, platform_id) for member_id, platform_id in member_ids if member_id) + member_id_list = ( + (member_id, platform_id) + for member_id, platform_id in member_ids + if member_id + ) # Grab the first one and craft the query data member_id, platform_id = next(member_id_list) - query_data = dict( - member_id=member_id, - platform_id=platform_id - ) + query_data = dict(member_id=member_id, platform_id=platform_id) # Query for that member, if that fails create a skeleton entry member_db = await self.bot.database.get_member_by_platform(**query_data) @@ -89,19 +100,18 @@ async def register(self, ctx): member_db.discord_id = ctx.author.id member_db.bungie_access_token = bungie_access_token - member_db.bungie_refresh_token = user_info.get('refresh_token') + member_db.bungie_refresh_token = user_info.get("refresh_token") await member_db.save() - e = discord.Embed( - colour=constants.BLUE, - title="Full Registration Complete" - ) + e = discord.Embed(colour=constants.BLUE, title="Full Registration Complete") emojis = [] # Update platform roles to match connected accounts if ctx.guild: guild_roles_db = await Role.filter(guild__guild_id=ctx.guild.id) - member_platforms = [platform_id for member_id, platform_id in member_ids if member_id] + member_platforms = [ + platform_id for member_id, platform_id in member_ids if member_id + ] guild_roles = [ discord.utils.get(ctx.guild.roles, id=role_db.role_id) @@ -115,18 +125,23 @@ async def register(self, ctx): platform_ids = list(constants.PLATFORM_MAP.values()) platform_emojis = [ - constants.PLATFORM_EMOJI_MAP.get(platform_names[platform_ids.index(platform)]) + constants.PLATFORM_EMOJI_MAP.get( + platform_names[platform_ids.index(platform)] + ) for platform in member_platforms ] message = f"User {str(ctx.author)} ({ctx.author.id}) has registered" if platform_emojis: - emojis = ' '.join([str(self.bot.get_emoji(emoji)) for emoji in platform_emojis if emoji]) - e.add_field( - name="Platforms Connected", - value=emojis + emojis = " ".join( + [ + str(self.bot.get_emoji(emoji)) + for emoji in platform_emojis + if emoji + ] ) + e.add_field(name="Platforms Connected", value=emojis) message = f"{message} with platforms {emojis}" await embed.edit(embed=e) diff --git a/seraphsix/cogs/server.py b/seraphsix/cogs/server.py index 1988ecb..3b24cac 100644 --- a/seraphsix/cogs/server.py +++ b/seraphsix/cogs/server.py @@ -26,7 +26,7 @@ async def server(self, ctx): if ctx.invoked_subcommand is None: raise commands.CommandNotFound() - @server.group(name='set', invoke_without_command=True) + @server.group(name="set", invoke_without_command=True) @commands.guild_only() @commands.cooldown(rate=2, per=5, type=commands.BucketType.user) async def server_set(self, ctx): @@ -42,7 +42,7 @@ async def role(self, ctx): if ctx.invoked_subcommand is None: raise commands.CommandNotFound() - @role.group(name='set', invoke_without_command=True) + @role.group(name="set", invoke_without_command=True) @commands.guild_only() @commands.cooldown(rate=2, per=5, type=commands.BucketType.user) async def role_set(self, ctx): @@ -50,7 +50,7 @@ async def role_set(self, ctx): if ctx.invoked_subcommand is None: raise commands.CommandNotFound() - @role.group(name='show', invoke_without_command=True) + @role.group(name="show", invoke_without_command=True) @commands.guild_only() @commands.cooldown(rate=2, per=5, type=commands.BucketType.user) async def role_show(self, ctx): @@ -58,7 +58,7 @@ async def role_show(self, ctx): if ctx.invoked_subcommand is None: raise commands.CommandNotFound() - @role.group(name='clear', invoke_without_command=True) + @role.group(name="clear", invoke_without_command=True) @commands.guild_only() @commands.cooldown(rate=2, per=5, type=commands.BucketType.user) async def role_clear(self, ctx): @@ -74,7 +74,7 @@ async def channel(self, ctx): if ctx.invoked_subcommand is None: raise commands.CommandNotFound() - @channel.group(name='set', invoke_without_command=True) + @channel.group(name="set", invoke_without_command=True) @commands.guild_only() @commands.cooldown(rate=2, per=5, type=commands.BucketType.user) async def set_channel(self, ctx): @@ -87,12 +87,14 @@ async def twitter_channel(self, ctx, twitter_id, message): manager = MessageManager(ctx) channel_db = await TwitterChannel.get_or_none( - guild_id=ctx.message.guild.id, - twitter_id=twitter_id + guild_id=ctx.message.guild.id, twitter_id=twitter_id ) if not channel_db: - details = {'guild_id': ctx.message.guild.id, - 'channel_id': ctx.message.channel.id, 'twitter_id': twitter_id} + details = { + "guild_id": ctx.message.guild.id, + "channel_id": ctx.message.channel.id, + "twitter_id": twitter_id, + } await TwitterChannel.create(**details) message = f"{message} now enabled and will post to **#{ctx.message.channel.name}**." else: @@ -107,7 +109,9 @@ async def twitter_channel(self, ctx, twitter_id, message): async def xboxsupport(self, ctx): """Enable sending tweets from XboxSupport to the current channel (Admin only)""" message = f"Xbox Support Information for **{ctx.message.guild.name}**" - self.bot.loop.create_task(self.twitter_channel(ctx, self.bot.TWITTER_XBOX_SUPPORT, message)) + self.bot.loop.create_task( + self.twitter_channel(ctx, self.bot.TWITTER_XBOX_SUPPORT, message) + ) @server.command() @twitter_enabled() @@ -116,7 +120,9 @@ async def xboxsupport(self, ctx): async def destinyreddit(self, ctx): """Enable sending tweets from r/DestinyTheGame to the current channel (Admin only)""" message = f"Destiny the Game Subreddit Posts for **{ctx.message.guild.name}**" - self.bot.loop.create_task(self.twitter_channel(ctx, self.bot.TWITTER_DESTINY_REDDIT, message)) + self.bot.loop.create_task( + self.twitter_channel(ctx, self.bot.TWITTER_DESTINY_REDDIT, message) + ) @server.command(help="Trigger initial setup of this server (Admin only)") @commands.guild_only() @@ -125,7 +131,9 @@ async def setup(self, ctx): """Initial setup of the server (Admin only)""" manager = MessageManager(ctx) await self.bot.database.create_guild(ctx.guild.id) - return await manager.send_and_clean(f"Server **{ctx.message.guild.name}** setup") + return await manager.send_and_clean( + f"Server **{ctx.message.guild.name}** setup" + ) @server.command() @commands.guild_only() @@ -135,9 +143,13 @@ async def clanlink(self, ctx, clan_id=None): manager = MessageManager(ctx) if not clan_id: - return await manager.send_and_clean("Command must include the Destiny clan ID") + return await manager.send_and_clean( + "Command must include the Destiny clan ID" + ) - group = await execute_pydest(self.bot.destiny.api.get_group, clan_id, return_type=DestinyGroupResponse) + group = await execute_pydest( + self.bot.destiny.api.get_group, clan_id, return_type=DestinyGroupResponse + ) clan_name = group.response.detail.name callsign = group.response.detail.clan_info.clan_callsign @@ -150,7 +162,8 @@ async def clanlink(self, ctx, clan_id=None): else: if clan_db.guild_id: return await manager.send_and_clean( - f"**{clan_name} [{callsign}]** is already linked to another server.") + f"**{clan_name} [{callsign}]** is already linked to another server." + ) else: guild_db = await Guild.get(guild_id=ctx.guild.id) clan_db.guild = guild_db @@ -159,7 +172,8 @@ async def clanlink(self, ctx, clan_id=None): await clan_db.save() return await manager.send_and_clean( - f"Server **{ctx.message.guild.name}** linked to **{clan_name} [{callsign}]**") + f"Server **{ctx.message.guild.name}** linked to **{clan_name} [{callsign}]**" + ) @server.command() @commands.guild_only() @@ -179,7 +193,7 @@ async def clanunlink(self, ctx): return await manager.send_and_clean(message) - @server_set.command(name='prefix') + @server_set.command(name="prefix") @commands.guild_only() @commands.has_permissions(manage_guild=True) async def setprefix(self, ctx, new_prefix): @@ -196,7 +210,7 @@ async def setprefix(self, ctx, new_prefix): return await manager.send_and_clean(message) - @server_set.command(name='platform') + @server_set.command(name="platform") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -215,7 +229,7 @@ async def setplatform(self, ctx, platform): return await manager.send_and_clean(message) - @set_channel.command(name='admin') + @set_channel.command(name="admin") @commands.guild_only() @commands.has_permissions(manage_guild=True) async def channelsetadmin(self, ctx, channel_id: int): @@ -236,7 +250,7 @@ async def channelsetadmin(self, ctx, channel_id: int): return await manager.send_and_clean(message) - @role_set.command(name='sherpa') + @role_set.command(name="sherpa") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -250,30 +264,29 @@ async def rolesetsherpa(self, ctx): while cont: name = await manager.send_and_get_response( "Enter the name of one or more roles that denote(s) a 'sherpa', one per line. " - "(enter `stop` when done, or enter `cancel` to cancel command entirely)") - if name.lower() == 'cancel': - return await manager.send_and_clean('Canceling command') - elif name.lower() == 'stop': + "(enter `stop` when done, or enter `cancel` to cancel command entirely)" + ) + if name.lower() == "cancel": + return await manager.send_and_clean("Canceling command") + elif name.lower() == "stop": cont = False else: role_obj = discord.utils.get(ctx.guild.roles, name=name) if role_obj: roles.append( - Role( - guild=guild_db, - role_id=role_obj.id, - is_sherpa=True - ) + Role(guild=guild_db, role_id=role_obj.id, is_sherpa=True) ) else: - return await manager.send_and_clean(f"Could not find a role with name `{name}`") + return await manager.send_and_clean( + f"Could not find a role with name `{name}`" + ) if roles: await Role.bulk_create(roles) return await manager.send_and_clean("Sherpa roles have been set") - @role_show.command(name='sherpa') + @role_show.command(name="sherpa") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -291,7 +304,7 @@ async def roleshowsherpa(self, ctx): base_embed = discord.Embed( color=constants.BLUE, title=f"Sherpa Roles for {ctx.guild.name}", - description=', '.join(roles) + description=", ".join(roles), ) await manager.send_embed(base_embed, clean=True) @@ -308,15 +321,21 @@ async def syncsherpas(self, ctx): return await manager.send_message( f"Sherpa tracking is not enabled on this server. " f"Please run `{ctx.prefix}server sherpatracking` first.", - mention=False, clean=False) + mention=False, + clean=False, + ) added, removed = await store_sherpas(self.bot, guild_db) embed = discord.Embed( - color=constants.BLUE, - title=f"Sherpas synced for {ctx.guild.name}" + color=constants.BLUE, title=f"Sherpas synced for {ctx.guild.name}" + ) + embed.add_field( + name="Added", value=", ".join([str(sherpa) for sherpa in added]) or "None" + ) + embed.add_field( + name="Removed", + value=", ".join([str(sherpa) for sherpa in removed]) or "None", ) - embed.add_field(name="Added", value=', '.join([str(sherpa) for sherpa in added]) or 'None') - embed.add_field(name="Removed", value=', '.join([str(sherpa) for sherpa in removed]) or 'None') await manager.send_embed(embed, clean=True) @@ -330,20 +349,20 @@ async def sherpatracking(self, ctx): guild_db = await Guild.get(guild_id=ctx.guild.id) reactions = { - constants.EMOJI_CHECKMARK: 'True', - constants.EMOJI_CROSSMARK: 'False' + constants.EMOJI_CHECKMARK: "True", + constants.EMOJI_CROSSMARK: "False", } react = await manager.send_message_react( f"Enable sherpa role tracking for {ctx.guild.name}?", reactions=reactions.keys(), clean=False, - with_cancel=True + with_cancel=True, ) if not react: return await manager.send_and_clean("Canceling command") - track = reactions[react] == 'True' + track = reactions[react] == "True" await guild_db.update(track_sherpas=track) message = "Sherpa tracking has been" @@ -354,7 +373,7 @@ async def sherpatracking(self, ctx): return await manager.send_message(message, mention=False, clean=False) - @role_set.command(name='platforms') + @role_set.command(name="platforms") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -367,8 +386,9 @@ async def rolesetplatform(self, ctx): for role, emoji in constants.PLATFORM_EMOJI_MAP.items(): name = await manager.send_and_get_response( f"Enter the name of the role to assign for {self.bot.get_emoji(emoji)} " - f"(enter `cancel` to cancel command)") - if name.lower() == 'cancel': + f"(enter `cancel` to cancel command)" + ) + if name.lower() == "cancel": return await manager.send_and_clean("Canceling command") else: role_obj = discord.utils.get(ctx.guild.roles, name=name) @@ -377,18 +397,20 @@ async def rolesetplatform(self, ctx): Role( guild=guild_db, role_id=role_obj.id, - platform_id=constants.PLATFORM_MAP[role] + platform_id=constants.PLATFORM_MAP[role], ) ) else: - return await manager.send_and_clean(f"Could not find a role with name `{name}`") + return await manager.send_and_clean( + f"Could not find a role with name `{name}`" + ) if roles: await Role.bulk_create(roles) return await manager.send_and_clean("Platforms have been set") - @role_show.command(name='platform') + @role_show.command(name="platform") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -398,27 +420,24 @@ async def roleshowplatform(self, ctx): guild_db = await Guild.get(guild_id=ctx.guild.id) base_embed = discord.Embed( - color=constants.BLUE, - title=f"Platform Roles for {ctx.guild.name}" + color=constants.BLUE, title=f"Platform Roles for {ctx.guild.name}" ) for role, emoji in constants.PLATFORM_EMOJI_MAP.items(): - role_db = await Role.get_or_none(guild=guild_db, platform_id=constants.PLATFORM_MAP[role]) + role_db = await Role.get_or_none( + guild=guild_db, platform_id=constants.PLATFORM_MAP[role] + ) if not role_db: role_name = "None" else: role_obj = discord.utils.get(ctx.guild.roles, id=role_db.role_id) role_name = role_obj.name - kwargs = dict( - name=self.bot.get_emoji(emoji), - value=role_name, - inline=True - ) + kwargs = dict(name=self.bot.get_emoji(emoji), value=role_name, inline=True) base_embed.add_field(**kwargs) await manager.send_embed(base_embed, clean=True) - @role_clear.command(name='platform') + @role_clear.command(name="platform") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -428,31 +447,30 @@ async def roleclearplatform(self, ctx): guild_db = await Guild.get(guild_id=ctx.guild.id) base_embed = discord.Embed( - color=constants.BLUE, - title=f"Platform Roles for {ctx.guild.name}" + color=constants.BLUE, title=f"Platform Roles for {ctx.guild.name}" ) for role, emoji in constants.PLATFORM_EMOJI_MAP.items(): - role_db = await Role.get(guild=guild_db, platform_id=constants.PLATFORM_MAP[role]) + role_db = await Role.get( + guild=guild_db, platform_id=constants.PLATFORM_MAP[role] + ) role_obj = discord.utils.get(ctx.guild.roles, id=role_db.role_id) kwargs = dict( - name=self.bot.get_emoji(emoji), - value=role_obj.name, - inline=True + name=self.bot.get_emoji(emoji), value=role_obj.name, inline=True ) base_embed.add_field(**kwargs) await manager.send_embed(base_embed, clean=True) clear_reactions = { - constants.EMOJI_CHECKMARK: 'clear', - constants.EMOJI_CROSSMARK: '' + constants.EMOJI_CHECKMARK: "clear", + constants.EMOJI_CROSSMARK: "", } clear = await manager.send_message_react( "Clear platform roles?", reactions=clear_reactions.keys(), clean=False, - with_cancel=True + with_cancel=True, ) if not clear: @@ -461,7 +479,7 @@ async def roleclearplatform(self, ctx): await Role.filter(guild=guild_db).delete() return await manager.send_and_clean("Platform roles cleared") - @role_set.command(name='protectedmember') + @role_set.command(name="protectedmember") @clan_is_linked() @commands.guild_only() @commands.has_permissions(manage_guild=True) @@ -475,10 +493,11 @@ async def rolesetprotectedmember(self, ctx): while cont: name = await manager.send_and_get_response( "Enter the name of one or more roles that denote(s) a 'protected member', one per line. " - "(enter `stop` when done, or enter `cancel` to cancel command entirely)") - if name.lower() == 'cancel': - return await manager.send_and_clean('Canceling command') - elif name.lower() == 'stop': + "(enter `stop` when done, or enter `cancel` to cancel command entirely)" + ) + if name.lower() == "cancel": + return await manager.send_and_clean("Canceling command") + elif name.lower() == "stop": cont = False else: role_obj = discord.utils.get(ctx.guild.roles, name=name) @@ -487,11 +506,13 @@ async def rolesetprotectedmember(self, ctx): Role( guild=guild_db, role_id=role_obj.id, - is_protected_clanmember=True + is_protected_clanmember=True, ) ) else: - return await manager.send_and_clean(f"Could not find a role with name `{name}`") + return await manager.send_and_clean( + f"Could not find a role with name `{name}`" + ) if roles: await Role.bulk_create(roles) diff --git a/seraphsix/cogs/utils/checks.py b/seraphsix/cogs/utils/checks.py index 4ee52da..335da10 100644 --- a/seraphsix/cogs/utils/checks.py +++ b/seraphsix/cogs/utils/checks.py @@ -5,23 +5,31 @@ from seraphsix.constants import SUPPORTED_GAME_MODES, CLAN_MEMBER_ADMIN from seraphsix.errors import ( - ConfigurationError, InvalidAdminError, InvalidCommandError, InvalidGameModeError, - InvalidMemberError, NotRegisteredError, MissingTimezoneError) + ConfigurationError, + InvalidAdminError, + InvalidCommandError, + InvalidGameModeError, + InvalidMemberError, + NotRegisteredError, + MissingTimezoneError, +) from seraphsix.models.database import Member, ClanMember def is_event(message): - '''Check if a message contains event data''' + """Check if a message contains event data""" if len(message.embeds) > 0: embed = message.embeds[0] - return (message.channel.name == 'upcoming-events' - and embed.fields - and embed.fields[0] - and embed.fields[1] - and embed.fields[2] - and embed.fields[0].name == "Time" - and embed.fields[1].name.startswith("Accepted") - and embed.fields[2].name.startswith("Declined")) + return ( + message.channel.name == "upcoming-events" + and embed.fields + and embed.fields[0] + and embed.fields[1] + and embed.fields[2] + and embed.fields[0].name == "Time" + and embed.fields[1].name.startswith("Accepted") + and embed.fields[2].name.startswith("Declined") + ) def is_int(x): @@ -49,10 +57,12 @@ def predicate(ctx): game_mode = ctx.message.content.split()[2] except IndexError: raise InvalidCommandError( - f"Missing game mode, supported are `{', '.join(SUPPORTED_GAME_MODES.keys())}`") + f"Missing game mode, supported are `{', '.join(SUPPORTED_GAME_MODES.keys())}`" + ) if game_mode in SUPPORTED_GAME_MODES.keys(): return True raise InvalidGameModeError(game_mode, SUPPORTED_GAME_MODES.keys()) + return commands.check(predicate) @@ -67,17 +77,19 @@ async def check_clan_linked(ctx): try: await ctx.bot.database.get_clans_by_guild(ctx.guild.id) except DoesNotExist: - raise ConfigurationError(( - f"Server **{ctx.message.guild.name}** has not been linked to " - f"a Destiny clan, please run `{ctx.prefix}server clanlink` first")) + raise ConfigurationError( + ( + f"Server **{ctx.message.guild.name}** has not been linked to " + f"a Destiny clan, please run `{ctx.prefix}server clanlink` first" + ) + ) return True async def check_clan_member(ctx): try: await ClanMember.get( - clan__guild__guild_id=ctx.message.guild.id, - member__discord_id=ctx.author.id + clan__guild__guild_id=ctx.message.guild.id, member__discord_id=ctx.author.id ) except DoesNotExist: raise InvalidMemberError @@ -96,18 +108,21 @@ async def check_timezone(ctx): def is_registered(): async def predicate(ctx): return await check_registered(ctx) + return commands.check(predicate) def member_has_timezone(): async def predicate(ctx): return await check_timezone(ctx) + return commands.check(predicate) def clan_is_linked(): async def predicate(ctx): return await check_clan_linked(ctx) + return commands.check(predicate) @@ -115,6 +130,7 @@ def is_clan_member(): async def predicate(ctx): await check_clan_linked(ctx) await check_clan_member(ctx) + return commands.check(predicate) @@ -127,17 +143,19 @@ async def predicate(ctx): await ClanMember.get( clan__guild__guild_id=ctx.message.guild.id, member_type__gte=CLAN_MEMBER_ADMIN, - member__discord_id=ctx.author.id + member__discord_id=ctx.author.id, ) except DoesNotExist: raise InvalidAdminError return True + return commands.check(predicate) def twitter_enabled(): def predicate(ctx): - if hasattr(ctx.bot, 'twitter'): + if hasattr(ctx.bot, "twitter"): return True raise ConfigurationError("Twitter support is not enabled at the bot level") + return commands.check(predicate) diff --git a/seraphsix/cogs/utils/helpers.py b/seraphsix/cogs/utils/helpers.py index d984222..d2075e9 100644 --- a/seraphsix/cogs/utils/helpers.py +++ b/seraphsix/cogs/utils/helpers.py @@ -49,7 +49,7 @@ def string_to_date(date, date_format=DATE_FORMAT): def get_timezone_name(timezone, country_code): set_zones = set() # See if it's already a valid 'long' time zone name - if '/' in timezone and timezone in pytz.all_timezones: + if "/" in timezone and timezone in pytz.all_timezones: set_zones.add(timezone) return set_zones @@ -75,7 +75,9 @@ def get_timezone_name(timezone, country_code): for name in timezones: tzone = pytz.timezone(name) - transition_info = getattr(tzone, '_transition_info', [[None, None, datetime.now(tzone).tzname()]]) + transition_info = getattr( + tzone, "_transition_info", [[None, None, datetime.now(tzone).tzname()]] + ) for utcoffset, dstoffset, tzabbrev in transition_info: if tzabbrev.upper() == timezone.upper(): set_zones.add(name) @@ -86,9 +88,8 @@ def get_timezone_name(timezone, country_code): async def get_requestor(ctx, include_clan=False): if include_clan: requestor_db = ClanMember.get_or_none( - clan__guild__guild_id=ctx.guild.id, - member__discord_id=ctx.author.id - ).prefetch_related('clan', 'member') + clan__guild__guild_id=ctx.guild.id, member__discord_id=ctx.author.id + ).prefetch_related("clan", "member") else: requestor_db = await Member.get_or_none(discord_id=ctx.author.id) return await requestor_db diff --git a/seraphsix/cogs/utils/message_manager.py b/seraphsix/cogs/utils/message_manager.py index 35bd9f8..31f174a 100644 --- a/seraphsix/cogs/utils/message_manager.py +++ b/seraphsix/cogs/utils/message_manager.py @@ -5,7 +5,6 @@ class MessageManager: - def __init__(self, ctx, trigger_typing=True): if trigger_typing: asyncio.create_task(ctx.trigger_typing()) @@ -27,6 +26,7 @@ async def send_and_clean(self, message, mention=True): async def clean_messages(self): """Delete messages marked for cleaning""" + def message_needs_cleaning(message): if message.id in [m.id for m in self.messages_to_clean]: return True @@ -37,19 +37,30 @@ def message_needs_cleaning(message): async def get_next_message(self): """Get the next message sent by the user in ctx.channel - Raises: asyncio.TimeoutError + Raises: asyncio.TimeoutError """ + def is_channel_message(message): - return message.author == self.ctx.author and message.channel == self.ctx.channel - return await self.ctx.bot.wait_for('message', check=is_channel_message, timeout=115) + return ( + message.author == self.ctx.author + and message.channel == self.ctx.channel + ) + + return await self.ctx.bot.wait_for( + "message", check=is_channel_message, timeout=115 + ) async def get_next_private_message(self): """Get the next private message sent by the user - Raises: asyncio.TimeoutError + Raises: asyncio.TimeoutError """ + def is_private_message(message): return message.author.dm_channel == self.ctx.author.dm_channel - return await self.ctx.bot.wait_for('message', check=is_private_message, timeout=120) + + return await self.ctx.bot.wait_for( + "message", check=is_private_message, timeout=120 + ) async def send_embed(self, embed, content=None, clean=False, channel_id=None): """Send an embed message to the user on ctx.channel""" @@ -65,7 +76,9 @@ async def send_embed(self, embed, content=None, clean=False, channel_id=None): self.messages_to_clean.append(msg) return msg - async def send_message(self, message_text, mention=True, clean=True, channel_id=None): + async def send_message( + self, message_text, mention=True, clean=True, channel_id=None + ): """Send a message to the user on ctx.channel""" if is_private_channel(self.ctx.channel): return await self.send_private_message(message_text) @@ -99,7 +112,9 @@ async def send_private_message(self, message_text): """Send a private message to the user""" return await self.ctx.author.send(message_text) - async def send_message_react(self, message_text, reactions=[], embed=None, clean=True, with_cancel=False): + async def send_message_react( + self, message_text, reactions=[], embed=None, clean=True, with_cancel=False + ): reactions = list(reactions) self.reaction_emojis = [] if embed: @@ -121,7 +136,9 @@ async def send_message_react(self, message_text, reactions=[], embed=None, clean retval = None while self.waiting: try: - reaction, user = await self.ctx.bot.wait_for('reaction_add', check=self.react_check, timeout=120.0) + reaction, user = await self.ctx.bot.wait_for( + "reaction_add", check=self.react_check, timeout=120.0 + ) except asyncio.TimeoutError: self.waiting = False try: diff --git a/seraphsix/cogs/utils/paginator.py b/seraphsix/cogs/utils/paginator.py index ea33dc2..6f96b34 100644 --- a/seraphsix/cogs/utils/paginator.py +++ b/seraphsix/cogs/utils/paginator.py @@ -39,7 +39,16 @@ class Pages: Our permissions for the channel. """ - def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True, title=None, color=None): + def __init__( + self, + ctx, + *, + entries, + per_page=12, + show_entry_count=True, + title=None, + color=None, + ): embed_color = color or discord.Colour.blurple() self.bot = ctx.bot self.entries = entries @@ -55,15 +64,19 @@ def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True, title=No self.paginating = len(entries) > per_page self.show_entry_count = show_entry_count self.reaction_emojis = [ - ('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', - self.first_page), - ('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page), - ('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page), - ('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', - self.last_page), - ('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page), - ('\N{BLACK SQUARE FOR STOP}', self.stop_pages), - ('\N{INFORMATION SOURCE}', self.show_help), + ( + "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}", + self.first_page, + ), + ("\N{BLACK LEFT-POINTING TRIANGLE}", self.previous_page), + ("\N{BLACK RIGHT-POINTING TRIANGLE}", self.next_page), + ( + "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}", + self.last_page, + ), + ("\N{INPUT SYMBOL FOR NUMBERS}", self.numbered_page), + ("\N{BLACK SQUARE FOR STOP}", self.stop_pages), + ("\N{INFORMATION SOURCE}", self.show_help), ] if ctx.guild is not None: @@ -72,24 +85,24 @@ def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True, title=No self.permissions = self.channel.permissions_for(ctx.bot.user) if not self.permissions.embed_links: - raise CannotPaginate('Bot does not have embed links permission.') + raise CannotPaginate("Bot does not have embed links permission.") if not self.permissions.send_messages: - raise CannotPaginate('Bot cannot send messages.') + raise CannotPaginate("Bot cannot send messages.") if self.paginating: # verify we can actually use the pagination session if not self.permissions.add_reactions: - raise CannotPaginate( - 'Bot does not have add reactions permission.') + raise CannotPaginate("Bot does not have add reactions permission.") if not self.permissions.read_message_history: raise CannotPaginate( - 'Bot does not have Read Message History permission.') + "Bot does not have Read Message History permission." + ) def get_page(self, page): base = (page - 1) * self.per_page - return self.entries[base:base + self.per_page] + return self.entries[base : base + self.per_page] def get_content(self, entries, page, *, first=False): return None @@ -101,27 +114,28 @@ def get_embed(self, entries, page, *, first=False): def prepare_embed(self, entries, page, *, first=False): p = [] for index, entry in enumerate(entries, 1 + ((page - 1) * self.per_page)): - p.append(f'{index}. {entry}') + p.append(f"{index}. {entry}") if self.maximum_pages > 1: if self.show_entry_count: - text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)' + text = f"Page {page}/{self.maximum_pages} ({len(self.entries)} entries)" else: - text = f'Page {page}/{self.maximum_pages}' + text = f"Page {page}/{self.maximum_pages}" self.embed.set_footer(text=text) if self.paginating and first: - p.append('') - p.append( - 'Confused? React with \N{INFORMATION SOURCE} for more info.') + p.append("") + p.append("Confused? React with \N{INFORMATION SOURCE} for more info.") - self.embed.description = '\n'.join(p) + self.embed.description = "\n".join(p) async def show_page(self, page, *, first=False): self.current_page = page entries = self.get_page(page) - content = self.get_content(entries, page, first=first) # pylint: disable=assignment-from-none + content = self.get_content( + entries, page, first=first + ) # pylint: disable=assignment-from-none embed = self.get_embed(entries, page, first=first) if not self.paginating: @@ -133,7 +147,7 @@ async def show_page(self, page, *, first=False): self.message = await self.channel.send(content=content, embed=embed) for (reaction, _) in self.reaction_emojis: - if self.maximum_pages == 2 and reaction in ('\u23ed', '\u23ee'): + if self.maximum_pages == 2 and reaction in ("\u23ed", "\u23ee"): # no |<< or >>| buttons if we only have two pages # we can't forbid it if someone ends up using it but remove # it from the default set @@ -168,17 +182,19 @@ async def show_current_page(self): async def numbered_page(self): """lets you type a page number to go to""" to_delete = [] - to_delete.append(await self.channel.send('What page do you want to go to?')) + to_delete.append(await self.channel.send("What page do you want to go to?")) def message_check(m): - return m.author == self.author and \ - self.channel == m.channel and \ - m.content.isdigit() + return ( + m.author == self.author + and self.channel == m.channel + and m.content.isdigit() + ) try: - msg = await self.bot.wait_for('message', check=message_check, timeout=30.0) + msg = await self.bot.wait_for("message", check=message_check, timeout=30.0) except asyncio.TimeoutError: - to_delete.append(await self.channel.send('Took too long.')) + to_delete.append(await self.channel.send("Took too long.")) await asyncio.sleep(5) else: page = int(msg.content) @@ -186,7 +202,11 @@ def message_check(m): if page != 0 and page <= self.maximum_pages: await self.show_page(page) else: - to_delete.append(await self.channel.send(f'Invalid page given. ({page}/{self.maximum_pages})')) + to_delete.append( + await self.channel.send( + f"Invalid page given. ({page}/{self.maximum_pages})" + ) + ) await asyncio.sleep(5) try: @@ -196,18 +216,21 @@ def message_check(m): async def show_help(self): """shows this message""" - messages = ['Welcome to the interactive paginator!\n'] - messages.append('This interactively allows you to see pages of text by navigating with ' - 'reactions. They are as follows:\n') + messages = ["Welcome to the interactive paginator!\n"] + messages.append( + "This interactively allows you to see pages of text by navigating with " + "reactions. They are as follows:\n" + ) for (emoji, func) in self.reaction_emojis: - messages.append(f'{emoji} {func.__doc__}') + messages.append(f"{emoji} {func.__doc__}") embed = self.embed.copy() embed.clear_fields() - embed.description = '\n'.join(messages) + embed.description = "\n".join(messages) embed.set_footer( - text=f'We were on page {self.current_page} before this message.') + text=f"We were on page {self.current_page} before this message." + ) await self.message.edit(content=None, embed=embed) async def go_back_to_current_page(): @@ -245,7 +268,9 @@ async def paginate(self): while self.paginating: try: - reaction, user = await self.bot.wait_for('reaction_add', check=self.react_check, timeout=120.0) + reaction, user = await self.bot.wait_for( + "reaction_add", check=self.react_check, timeout=120.0 + ) except asyncio.TimeoutError: self.paginating = False try: @@ -277,9 +302,9 @@ def prepare_embed(self, entries, page, *, first=False): if self.maximum_pages > 1: if self.show_entry_count: - text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)' + text = f"Page {page}/{self.maximum_pages} ({len(self.entries)} entries)" else: - text = f'Page {page}/{self.maximum_pages}' + text = f"Page {page}/{self.maximum_pages}" self.embed.set_footer(text=text) @@ -287,13 +312,16 @@ def prepare_embed(self, entries, page, *, first=False): class TextPages(Pages): """Uses a commands.Paginator internally to paginate some text.""" - def __init__(self, ctx, text, *, prefix='```', suffix='```', max_size=2000): + def __init__(self, ctx, text, *, prefix="```", suffix="```", max_size=2000): paginator = CommandPaginator( - prefix=prefix, suffix=suffix, max_size=max_size - 200) - for line in text.split('\n'): + prefix=prefix, suffix=suffix, max_size=max_size - 200 + ) + for line in text.split("\n"): paginator.add_line(line) - super().__init__(ctx, entries=paginator.pages, per_page=1, show_entry_count=False) + super().__init__( + ctx, entries=paginator.pages, per_page=1, show_entry_count=False + ) def get_page(self, page): return self.entries[page - 1] @@ -303,7 +331,7 @@ def get_embed(self, entries, page, *, first=False): def get_content(self, entry, page, *, first=False): if self.maximum_pages > 1: - return f'{entry}\nPage {page}/{self.maximum_pages}' + return f"{entry}\nPage {page}/{self.maximum_pages}" return entry @@ -322,5 +350,5 @@ def prepare_embed(self, entries, page, *, first=False): if self.maximum_pages > 1: text = f"Page {page}/{self.maximum_pages}" footer.append(text) - + self.embed.set_footer(text=" | ".join(footer)) diff --git a/seraphsix/constants.py b/seraphsix/constants.py index c260ef4..d68af1e 100644 --- a/seraphsix/constants.py +++ b/seraphsix/constants.py @@ -3,13 +3,13 @@ from datetime import datetime -DATE_FORMAT = '%Y-%m-%d %H:%M:%S' -DATE_FORMAT_TZ = f'{DATE_FORMAT} %Z' +DATE_FORMAT = "%Y-%m-%d %H:%M:%S" +DATE_FORMAT_TZ = f"{DATE_FORMAT} %Z" TIME_HOUR_SECONDS = 3600 TIME_MIN_SECONDS = 60 -ROOT_LOG_LEVEL = 'INFO' +ROOT_LOG_LEVEL = "INFO" -LOG_FORMAT_MSG = '%(asctime)s %(name)s[%(process)d]: %(levelname)s %(message)s' +LOG_FORMAT_MSG = "%(asctime)s %(name)s[%(process)d]: %(levelname)s %(message)s" DB_MAX_CONNECTIONS = 20 ARQ_MAX_JOBS = 100 @@ -24,43 +24,65 @@ EMOJI_STEAM = 644904784088006656 EMOJI_STADIA = 644904919111041035 -EMOJI_LETTER_A = '\U0001F1E6' -EMOJI_LETTER_B = '\U0001F1E7' -EMOJI_LETTER_C = '\U0001F1E8' -EMOJI_LETTER_D = '\U0001F1E9' -EMOJI_LETTER_E = '\U0001F1EA' -EMOJI_LETTER_F = '\U0001F1EB' -EMOJI_LETTER_G = '\U0001F1EC' -EMOJI_LETTER_H = '\U0001F1ED' -EMOJI_LETTER_I = '\U0001F1EE' -EMOJI_LETTER_J = '\U0001F1EF' -EMOJI_LETTER_K = '\U0001F1F0' -EMOJI_LETTER_L = '\U0001F1F1' -EMOJI_LETTER_M = '\U0001F1F2' -EMOJI_LETTER_N = '\U0001F1F3' -EMOJI_LETTER_O = '\U0001F1F4' -EMOJI_LETTER_P = '\U0001F1F5' -EMOJI_LETTER_Q = '\U0001F1F6' -EMOJI_LETTER_R = '\U0001F1F7' -EMOJI_LETTER_S = '\U0001F1F8' -EMOJI_LETTER_T = '\U0001F1F9' -EMOJI_LETTER_U = '\U0001F1FA' -EMOJI_LETTER_V = '\U0001F1FB' -EMOJI_LETTER_W = '\U0001F1FC' -EMOJI_LETTER_X = '\U0001F1FD' -EMOJI_LETTER_Y = '\U0001F1FE' -EMOJI_LETTER_Z = '\U0001F1FF' +EMOJI_LETTER_A = "\U0001F1E6" +EMOJI_LETTER_B = "\U0001F1E7" +EMOJI_LETTER_C = "\U0001F1E8" +EMOJI_LETTER_D = "\U0001F1E9" +EMOJI_LETTER_E = "\U0001F1EA" +EMOJI_LETTER_F = "\U0001F1EB" +EMOJI_LETTER_G = "\U0001F1EC" +EMOJI_LETTER_H = "\U0001F1ED" +EMOJI_LETTER_I = "\U0001F1EE" +EMOJI_LETTER_J = "\U0001F1EF" +EMOJI_LETTER_K = "\U0001F1F0" +EMOJI_LETTER_L = "\U0001F1F1" +EMOJI_LETTER_M = "\U0001F1F2" +EMOJI_LETTER_N = "\U0001F1F3" +EMOJI_LETTER_O = "\U0001F1F4" +EMOJI_LETTER_P = "\U0001F1F5" +EMOJI_LETTER_Q = "\U0001F1F6" +EMOJI_LETTER_R = "\U0001F1F7" +EMOJI_LETTER_S = "\U0001F1F8" +EMOJI_LETTER_T = "\U0001F1F9" +EMOJI_LETTER_U = "\U0001F1FA" +EMOJI_LETTER_V = "\U0001F1FB" +EMOJI_LETTER_W = "\U0001F1FC" +EMOJI_LETTER_X = "\U0001F1FD" +EMOJI_LETTER_Y = "\U0001F1FE" +EMOJI_LETTER_Z = "\U0001F1FF" -EMOJI_LETTERS = [EMOJI_LETTER_A, EMOJI_LETTER_B, EMOJI_LETTER_C, EMOJI_LETTER_D, EMOJI_LETTER_E, - EMOJI_LETTER_F, EMOJI_LETTER_G, EMOJI_LETTER_H, EMOJI_LETTER_I, EMOJI_LETTER_J, - EMOJI_LETTER_K, EMOJI_LETTER_L, EMOJI_LETTER_M, EMOJI_LETTER_N, EMOJI_LETTER_O, - EMOJI_LETTER_P, EMOJI_LETTER_Q, EMOJI_LETTER_R, EMOJI_LETTER_S, EMOJI_LETTER_T, - EMOJI_LETTER_U, EMOJI_LETTER_V, EMOJI_LETTER_W, EMOJI_LETTER_X, EMOJI_LETTER_Y, - EMOJI_LETTER_Z] +EMOJI_LETTERS = [ + EMOJI_LETTER_A, + EMOJI_LETTER_B, + EMOJI_LETTER_C, + EMOJI_LETTER_D, + EMOJI_LETTER_E, + EMOJI_LETTER_F, + EMOJI_LETTER_G, + EMOJI_LETTER_H, + EMOJI_LETTER_I, + EMOJI_LETTER_J, + EMOJI_LETTER_K, + EMOJI_LETTER_L, + EMOJI_LETTER_M, + EMOJI_LETTER_N, + EMOJI_LETTER_O, + EMOJI_LETTER_P, + EMOJI_LETTER_Q, + EMOJI_LETTER_R, + EMOJI_LETTER_S, + EMOJI_LETTER_T, + EMOJI_LETTER_U, + EMOJI_LETTER_V, + EMOJI_LETTER_W, + EMOJI_LETTER_X, + EMOJI_LETTER_Y, + EMOJI_LETTER_Z, +] -EMOJI_STOP = '\N{BLACK SQUARE FOR STOP}' -EMOJI_CHECKMARK = '\u2705' -EMOJI_CROSSMARK = '\u274C' +EMOJI_STOP = "\N{BLACK SQUARE FOR STOP}" +EMOJI_CHECKMARK = "\u2705" +EMOJI_CROSSMARK = "\u274C" PLATFORM_XBOX = 1 PLATFORM_PSN = 2 @@ -74,30 +96,30 @@ PLATFORM_PSN, PLATFORM_BLIZZARD, PLATFORM_STEAM, - PLATFORM_STADIA + PLATFORM_STADIA, ] PLATFORM_MAP = { - 'xbox': PLATFORM_XBOX, - 'psn': PLATFORM_PSN, - 'blizzard': PLATFORM_BLIZZARD, - 'steam': PLATFORM_STEAM, - 'stadia': PLATFORM_STADIA, - 'bungie': PLATFORM_BUNGIE + "xbox": PLATFORM_XBOX, + "psn": PLATFORM_PSN, + "blizzard": PLATFORM_BLIZZARD, + "steam": PLATFORM_STEAM, + "stadia": PLATFORM_STADIA, + "bungie": PLATFORM_BUNGIE, } PLATFORM_EMOJI_MAP = { - 'psn': EMOJI_PSN, - 'xbox': EMOJI_XBOX, - 'steam': EMOJI_STEAM, - 'stadia': EMOJI_STADIA + "psn": EMOJI_PSN, + "xbox": EMOJI_XBOX, + "steam": EMOJI_STEAM, + "stadia": EMOJI_STADIA, } PLATFORM_EMOJI_ID = { EMOJI_PSN: PLATFORM_PSN, EMOJI_XBOX: PLATFORM_XBOX, EMOJI_STEAM: PLATFORM_STEAM, - EMOJI_STADIA: PLATFORM_STADIA + EMOJI_STADIA: PLATFORM_STADIA, } CLAN_MEMBER_NONE = 0 @@ -113,7 +135,7 @@ CLAN_MEMBER_MEMBER, CLAN_MEMBER_ADMIN, CLAN_MEMBER_ACTING_FOUNDER, - CLAN_MEMBER_FOUNDER + CLAN_MEMBER_FOUNDER, ] # https://bungie-net.github.io/#/components/schemas/Destiny.DestinyComponentType @@ -199,87 +221,162 @@ MODE_TRIALSOFOSIRIS = 84 MODES_PVP = [ - MODE_ALLMAYHEM, MODE_SUPREMACY, MODE_DOUBLES, - MODE_LOCKDOWN, MODE_BREAKTHROUGH, MODE_SHOWDOWN, - MODE_IRONBANNERCONTROL, MODE_IRONBANNERCLASH, - MODE_CLASHQUICKPLAY, MODE_CONTROLQUICKPLAY, MODE_MOMENTUM, - MODE_ELIMINATION, MODE_SURVIVAL, MODE_COUNTDOWN, - MODE_CLASHCOMPETITIVE, MODE_CONTROLCOMPETITIVE, - MODE_TRIALSOFOSIRIS, MODE_TRIALSCOUNTDOWN, MODE_TRIALSSURVIVAL, - MODE_CRIMSONDOUBLES, MODE_SCORCHEDTEAM + MODE_ALLMAYHEM, + MODE_SUPREMACY, + MODE_DOUBLES, + MODE_LOCKDOWN, + MODE_BREAKTHROUGH, + MODE_SHOWDOWN, + MODE_IRONBANNERCONTROL, + MODE_IRONBANNERCLASH, + MODE_CLASHQUICKPLAY, + MODE_CONTROLQUICKPLAY, + MODE_MOMENTUM, + MODE_ELIMINATION, + MODE_SURVIVAL, + MODE_COUNTDOWN, + MODE_CLASHCOMPETITIVE, + MODE_CONTROLCOMPETITIVE, + MODE_TRIALSOFOSIRIS, + MODE_TRIALSCOUNTDOWN, + MODE_TRIALSSURVIVAL, + MODE_CRIMSONDOUBLES, + MODE_SCORCHEDTEAM, ] -MODES_GAMBIT = [ - MODE_GAMBIT, MODE_GAMBITPRIME -] +MODES_GAMBIT = [MODE_GAMBIT, MODE_GAMBITPRIME] -MODES_STRIKE = [ - MODE_STRIKE, MODE_SCOREDNIGHTFALL -] +MODES_STRIKE = [MODE_STRIKE, MODE_SCOREDNIGHTFALL] MODES_PVE = [ - MODE_STRIKE, MODE_NIGHTFALL, MODE_MENAGERIE, MODE_SCOREDNIGHTFALL, - MODE_VEXOFFENSIVE, MODE_BLACKARMORYRUN, MODE_NIGHTMAREHUNT, MODE_RAID, - MODE_HEROICADVENTURE, MODE_THESUNDIAL, MODE_PATROL, MODE_STORY, - MODE_DUNGEON, MODE_RECKONING, MODE_SCOREDHEROICNIGHTFALL, MODE_HEROICNIGHTFALL + MODE_STRIKE, + MODE_NIGHTFALL, + MODE_MENAGERIE, + MODE_SCOREDNIGHTFALL, + MODE_VEXOFFENSIVE, + MODE_BLACKARMORYRUN, + MODE_NIGHTMAREHUNT, + MODE_RAID, + MODE_HEROICADVENTURE, + MODE_THESUNDIAL, + MODE_PATROL, + MODE_STORY, + MODE_DUNGEON, + MODE_RECKONING, + MODE_SCOREDHEROICNIGHTFALL, + MODE_HEROICNIGHTFALL, ] MODE_MAP = { - MODE_STORY: {'title': 'story', 'player_count': 3, 'threshold': 2}, - MODE_STRIKE: {'title': 'strike', 'player_count': 3, 'threshold': 2}, - MODE_RAID: {'title': 'raid', 'player_count': 6, 'threshold': 3}, - MODE_PATROL: {'title': 'patrol', 'player_count': 3, 'threshold': 2}, - MODE_NIGHTFALL: {'title': 'nightfall', 'player_count': 3, 'threshold': 2}, - MODE_HEROICNIGHTFALL: {'title': 'nightfall', 'player_count': 3, 'threshold': 2}, - MODE_SCOREDNIGHTFALL: {'title': 'nightfall', 'player_count': 3, 'threshold': 2}, - MODE_SCOREDHEROICNIGHTFALL: {'title': 'nightfall', 'player_count': 3, 'threshold': 2}, - MODE_BLACKARMORYRUN: {'title': 'forge', 'player_count': 3, 'threshold': 2}, - MODE_ALLMAYHEM: {'title': 'mayhem', 'player_count': 6, 'threshold': 3}, - MODE_SUPREMACY: {'title': 'supremacy', 'player_count': 4, 'threshold': 2}, - MODE_SURVIVAL: {'title': 'survival', 'player_count': 3, 'threshold': 2}, - MODE_COUNTDOWN: {'title': 'countdown', 'player_count': 4, 'threshold': 2}, - MODE_IRONBANNERCONTROL: {'title': 'ironbanner (control)', 'player_count': 6, 'threshold': 3}, - MODE_IRONBANNERCLASH: {'title': 'ironbanner (clash)', 'player_count': 6, 'threshold': 3}, - MODE_DOUBLES: {'title': 'doubles', 'player_count': 2, 'threshold': 2}, - MODE_CRIMSONDOUBLES: {'title': 'crimson doubles', 'player_count': 2, 'threshold': 2}, - MODE_LOCKDOWN: {'title': 'lockdown', 'player_count': 4, 'threshold': 2}, - MODE_SHOWDOWN: {'title': 'showdown', 'player_count': 4, 'threshold': 2}, - MODE_BREAKTHROUGH: {'title': 'breakthrough', 'player_count': 4, 'threshold': 2}, - MODE_CLASHQUICKPLAY: {'title': 'clash (quickplay)', 'player_count': 6, 'threshold': 3}, - MODE_CLASHCOMPETITIVE: {'title': 'clash (competitive)', 'player_count': 4, 'threshold': 2}, - MODE_CONTROLQUICKPLAY: {'title': 'control (quickplay)', 'player_count': 6, 'threshold': 3}, - MODE_CONTROLCOMPETITIVE: {'title': 'control (competitive)', 'player_count': 4, 'threshold': 2}, - MODE_GAMBIT: {'title': 'gambit', 'player_count': 4, 'threshold': 2}, - MODE_GAMBITPRIME: {'title': 'gambit prime', 'player_count': 4, 'threshold': 2}, - MODE_RECKONING: {'title': 'reckoning', 'player_count': 4, 'threshold': 2}, - MODE_MENAGERIE: {'title': 'menagerie', 'player_count': 6, 'threshold': 3}, - MODE_VEXOFFENSIVE: {'title': 'menagerie', 'player_count': 6, 'threshold': 3}, - MODE_NIGHTMAREHUNT: {'title': 'nightmare hunt', 'player_count': 3, 'threshold': 2}, - MODE_HEROICADVENTURE: {'title': 'heroic adventure', 'player_count': 3, 'threshold': 2}, - MODE_ELIMINATION: {'title': 'elimination', 'player_count': 3, 'threshold': 2}, - MODE_MOMENTUM: {'title': 'momentum control', 'player_count': 6, 'threshold': 3}, - MODE_THESUNDIAL: {'title': 'the sundial', 'player_count': 6, 'threshold': 3}, - MODE_TRIALSOFOSIRIS: {'title': 'trials of osiris', 'player_count': 3, 'threshold': 2}, - MODE_TRIALSCOUNTDOWN: {'title': 'trials of the nine', 'player_count': 3, 'threshold': 2}, - MODE_TRIALSSURVIVAL: {'title': 'trials of the nine', 'player_count': 3, 'threshold': 2}, - MODE_SCORCHEDTEAM: {'title': 'team scorched', 'player_count': 6, 'threshold': 3}, - MODE_DUNGEON: {'title': 'dungeon', 'player_count': 3, 'threshold': 2}, + MODE_STORY: {"title": "story", "player_count": 3, "threshold": 2}, + MODE_STRIKE: {"title": "strike", "player_count": 3, "threshold": 2}, + MODE_RAID: {"title": "raid", "player_count": 6, "threshold": 3}, + MODE_PATROL: {"title": "patrol", "player_count": 3, "threshold": 2}, + MODE_NIGHTFALL: {"title": "nightfall", "player_count": 3, "threshold": 2}, + MODE_HEROICNIGHTFALL: {"title": "nightfall", "player_count": 3, "threshold": 2}, + MODE_SCOREDNIGHTFALL: {"title": "nightfall", "player_count": 3, "threshold": 2}, + MODE_SCOREDHEROICNIGHTFALL: { + "title": "nightfall", + "player_count": 3, + "threshold": 2, + }, + MODE_BLACKARMORYRUN: {"title": "forge", "player_count": 3, "threshold": 2}, + MODE_ALLMAYHEM: {"title": "mayhem", "player_count": 6, "threshold": 3}, + MODE_SUPREMACY: {"title": "supremacy", "player_count": 4, "threshold": 2}, + MODE_SURVIVAL: {"title": "survival", "player_count": 3, "threshold": 2}, + MODE_COUNTDOWN: {"title": "countdown", "player_count": 4, "threshold": 2}, + MODE_IRONBANNERCONTROL: { + "title": "ironbanner (control)", + "player_count": 6, + "threshold": 3, + }, + MODE_IRONBANNERCLASH: { + "title": "ironbanner (clash)", + "player_count": 6, + "threshold": 3, + }, + MODE_DOUBLES: {"title": "doubles", "player_count": 2, "threshold": 2}, + MODE_CRIMSONDOUBLES: { + "title": "crimson doubles", + "player_count": 2, + "threshold": 2, + }, + MODE_LOCKDOWN: {"title": "lockdown", "player_count": 4, "threshold": 2}, + MODE_SHOWDOWN: {"title": "showdown", "player_count": 4, "threshold": 2}, + MODE_BREAKTHROUGH: {"title": "breakthrough", "player_count": 4, "threshold": 2}, + MODE_CLASHQUICKPLAY: { + "title": "clash (quickplay)", + "player_count": 6, + "threshold": 3, + }, + MODE_CLASHCOMPETITIVE: { + "title": "clash (competitive)", + "player_count": 4, + "threshold": 2, + }, + MODE_CONTROLQUICKPLAY: { + "title": "control (quickplay)", + "player_count": 6, + "threshold": 3, + }, + MODE_CONTROLCOMPETITIVE: { + "title": "control (competitive)", + "player_count": 4, + "threshold": 2, + }, + MODE_GAMBIT: {"title": "gambit", "player_count": 4, "threshold": 2}, + MODE_GAMBITPRIME: {"title": "gambit prime", "player_count": 4, "threshold": 2}, + MODE_RECKONING: {"title": "reckoning", "player_count": 4, "threshold": 2}, + MODE_MENAGERIE: {"title": "menagerie", "player_count": 6, "threshold": 3}, + MODE_VEXOFFENSIVE: {"title": "menagerie", "player_count": 6, "threshold": 3}, + MODE_NIGHTMAREHUNT: {"title": "nightmare hunt", "player_count": 3, "threshold": 2}, + MODE_HEROICADVENTURE: { + "title": "heroic adventure", + "player_count": 3, + "threshold": 2, + }, + MODE_ELIMINATION: {"title": "elimination", "player_count": 3, "threshold": 2}, + MODE_MOMENTUM: {"title": "momentum control", "player_count": 6, "threshold": 3}, + MODE_THESUNDIAL: {"title": "the sundial", "player_count": 6, "threshold": 3}, + MODE_TRIALSOFOSIRIS: { + "title": "trials of osiris", + "player_count": 3, + "threshold": 2, + }, + MODE_TRIALSCOUNTDOWN: { + "title": "trials of the nine", + "player_count": 3, + "threshold": 2, + }, + MODE_TRIALSSURVIVAL: { + "title": "trials of the nine", + "player_count": 3, + "threshold": 2, + }, + MODE_SCORCHEDTEAM: {"title": "team scorched", "player_count": 6, "threshold": 3}, + MODE_DUNGEON: {"title": "dungeon", "player_count": 3, "threshold": 2}, } SUPPORTED_GAME_MODES = { - 'gambit': MODES_GAMBIT, - 'pve': MODES_PVE, - 'pvp': MODES_PVP, - 'raid': [MODE_RAID], - 'all': MODES_PVP + MODES_GAMBIT + MODES_PVE + "gambit": MODES_GAMBIT, + "pve": MODES_PVE, + "pvp": MODES_PVP, + "raid": [MODE_RAID], + "all": MODES_PVP + MODES_GAMBIT + MODES_PVE, } -DESTINY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S%z' -DESTINY_DATE_FORMAT_MS = '%Y-%m-%dT%H:%M:%S.%f%z' -DESTINY_DATE_FORMAT_API = '%Y-%m-%dT%H:%M:%SZ' -FORSAKEN_RELEASE = datetime.strptime('2018-09-04T18:00:00Z', DESTINY_DATE_FORMAT).astimezone(tz=pytz.utc) -SHADOWKEEP_RELEASE = datetime.strptime('2019-10-01T18:00:00Z', DESTINY_DATE_FORMAT).astimezone(tz=pytz.utc) -BEYOND_LIGHT_RELEASE = datetime.strptime('2020-11-10T18:00:00Z', DESTINY_DATE_FORMAT).astimezone(tz=pytz.utc) +DESTINY_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z" +DESTINY_DATE_FORMAT_MS = "%Y-%m-%dT%H:%M:%S.%f%z" +DESTINY_DATE_FORMAT_API = "%Y-%m-%dT%H:%M:%SZ" +FORSAKEN_RELEASE = datetime.strptime( + "2018-09-04T18:00:00Z", DESTINY_DATE_FORMAT +).astimezone(tz=pytz.utc) +SHADOWKEEP_RELEASE = datetime.strptime( + "2019-10-01T18:00:00Z", DESTINY_DATE_FORMAT +).astimezone(tz=pytz.utc) +BEYOND_LIGHT_RELEASE = datetime.strptime( + "2020-11-10T18:00:00Z", DESTINY_DATE_FORMAT +).astimezone(tz=pytz.utc) TWITTER_DESTINY_REDDIT = 2608131020 TWITTER_XBOX_SUPPORT = 59804598 @@ -287,28 +384,27 @@ TWITTER_FOLLOW_USERS = [TWITTER_DESTINY_REDDIT, TWITTER_XBOX_SUPPORT] THE100_GAME_SORT_RULES = { - 'Destiny 2': { - 'fresh': [ - 'Raid - Garden of Salvation' - 'Raid - Crown of Sorrow', - 'Raid - Leviathan - Prestige', - 'Raid - Scourge of the Past', - 'Raid - Leviathan - Normal', - 'Raid - Last Wish' + "Destiny 2": { + "fresh": [ + "Raid - Garden of Salvation" "Raid - Crown of Sorrow", + "Raid - Leviathan - Prestige", + "Raid - Scourge of the Past", + "Raid - Leviathan - Normal", + "Raid - Last Wish", ], - 'normal': [ - 'Blind Well', - 'Escalation Protocol', - 'Quest - Outbreak Perfected', - 'Strike - Nightfall' + "normal": [ + "Blind Well", + "Escalation Protocol", + "Quest - Outbreak Perfected", + "Strike - Nightfall", ], - 'anything': [ - 'Quest' - ] + "anything": ["Quest"], } } -THE100_DATE_DISPLAY = '%m-%d %a %I:%M %p %Z' -THE100_DATE_CREATE = '%m/%d %I:%M%p' -THE100_LOGO_URL = ('https://www.the100.io/assets/the-100-logo-' - '01d3884b844d4308fcf20f19281cc758f7b9803e2fba6baa6dc915ab8b385ba7.png') +THE100_DATE_DISPLAY = "%m-%d %a %I:%M %p %Z" +THE100_DATE_CREATE = "%m/%d %I:%M%p" +THE100_LOGO_URL = ( + "https://www.the100.io/assets/the-100-logo-" + "01d3884b844d4308fcf20f19281cc758f7b9803e2fba6baa6dc915ab8b385ba7.png" +) diff --git a/seraphsix/database.py b/seraphsix/database.py index d4eadfb..cde60dc 100644 --- a/seraphsix/database.py +++ b/seraphsix/database.py @@ -4,7 +4,14 @@ from datetime import timedelta from seraphsix import constants from seraphsix.tasks.parsing import member_hash -from seraphsix.models.database import Member, Clan, ClanMember, GameMember, Game, ClanGame +from seraphsix.models.database import ( + Member, + Clan, + ClanMember, + GameMember, + Game, + ClanGame, +) from urllib.parse import urlparse @@ -18,7 +25,6 @@ class Database(object): - def __init__(self, url, max_connections=constants.DB_MAX_CONNECTIONS): self.url = urlparse(url) self.max_size = max_connections @@ -26,24 +32,24 @@ def __init__(self, url, max_connections=constants.DB_MAX_CONNECTIONS): async def initialize(self): await Tortoise.init( config={ - 'connections': { - 'default': { - 'engine': 'tortoise.backends.asyncpg', - 'credentials': { - 'host': self.url.hostname, - 'port': self.url.port, - 'user': self.url.username, - 'password': self.url.password, - 'database': self.url.path[1:], - 'max_size': self.max_size - } + "connections": { + "default": { + "engine": "tortoise.backends.asyncpg", + "credentials": { + "host": self.url.hostname, + "port": self.url.port, + "user": self.url.username, + "password": self.url.password, + "database": self.url.path[1:], + "max_size": self.max_size, + }, } }, - 'apps': { - 'seraphsix': { - 'models': ['seraphsix.models.database'], + "apps": { + "seraphsix": { + "models": ["seraphsix.models.database"], } - } + }, } ) @@ -66,24 +72,40 @@ async def get_member_by_naive_username(self, username, include_clan=True): username = username.lower() if include_clan: - member_db = await ClanMember.annotate( - bungie_username_lo=Lower('member__bungie_username'), xbox_username_lo=Lower('member__xbox_username'), - psn_username_lo=Lower('member__psn_username'), blizzard_username_lo=Lower('member__blizzard_username'), - steam_username_lo=Lower('member__steam_username'), stadia_username_lo=Lower('member__stadia_username') - ).get_or_none( - Q(bungie_username_lo=username) | Q(xbox_username_lo=username) | - Q(psn_username_lo=username) | Q(blizzard_username_lo=username) | - Q(steam_username_lo=username) | Q(stadia_username_lo=username) - ).prefetch_related('clan', 'member') + member_db = ( + await ClanMember.annotate( + bungie_username_lo=Lower("member__bungie_username"), + xbox_username_lo=Lower("member__xbox_username"), + psn_username_lo=Lower("member__psn_username"), + blizzard_username_lo=Lower("member__blizzard_username"), + steam_username_lo=Lower("member__steam_username"), + stadia_username_lo=Lower("member__stadia_username"), + ) + .get_or_none( + Q(bungie_username_lo=username) + | Q(xbox_username_lo=username) + | Q(psn_username_lo=username) + | Q(blizzard_username_lo=username) + | Q(steam_username_lo=username) + | Q(stadia_username_lo=username) + ) + .prefetch_related("clan", "member") + ) else: member_db = await Member.annotate( - bungie_username_lo=Lower('bungie_username'), xbox_username_lo=Lower('xbox_username'), - psn_username_lo=Lower('psn_username'), blizzard_username_lo=Lower('blizzard_username'), - steam_username_lo=Lower('steam_username'), stadia_username_lo=Lower('stadia_username') + bungie_username_lo=Lower("bungie_username"), + xbox_username_lo=Lower("xbox_username"), + psn_username_lo=Lower("psn_username"), + blizzard_username_lo=Lower("blizzard_username"), + steam_username_lo=Lower("steam_username"), + stadia_username_lo=Lower("stadia_username"), ).get_or_none( - Q(bungie_username_lo=username) | Q(xbox_username_lo=username) | - Q(psn_username_lo=username) | Q(blizzard_username_lo=username) | - Q(steam_username_lo=username) | Q(stadia_username_lo=username) + Q(bungie_username_lo=username) + | Q(xbox_username_lo=username) + | Q(psn_username_lo=username) + | Q(blizzard_username_lo=username) + | Q(steam_username_lo=username) + | Q(stadia_username_lo=username) ) return member_db @@ -109,35 +131,43 @@ async def get_member_by_platform_username(self, username, platform_id): username = username.lower() if platform_id == constants.PLATFORM_BUNGIE: - username_field = 'bungie_username' + username_field = "bungie_username" elif platform_id == constants.PLATFORM_PSN: - username_field = 'psn_username' + username_field = "psn_username" elif platform_id == constants.PLATFORM_XBOX: - username_field = 'xbox_username' + username_field = "xbox_username" elif platform_id == constants.PLATFORM_BLIZZARD: - username_field = 'blizzard_username' + username_field = "blizzard_username" elif platform_id == constants.PLATFORM_STEAM: - username_field = 'steam_username' + username_field = "steam_username" elif platform_id == constants.PLATFORM_STADIA: - username_field = 'stadia_username' + username_field = "stadia_username" - query = await Member.annotate( - name_lo=Lower(username_field) - ).get_or_none(name_lo=username).prefetch_related('clans') + query = ( + await Member.annotate(name_lo=Lower(username_field)) + .get_or_none(name_lo=username) + .prefetch_related("clans") + ) return query async def get_member_by_discord_id(self, discord_id, include_clan=True): if include_clan: - query = ClanMember.get_or_none(member__discord_id=discord_id).prefetch_related('member', 'clan') + query = ClanMember.get_or_none( + member__discord_id=discord_id + ).prefetch_related("member", "clan") else: query = Member.get_or_none(discord_id=discord_id) return await query async def get_clan_members(self, clan_ids): - return await ClanMember.filter(clan__clan_id__in=clan_ids).prefetch_related('member', 'clan') + return await ClanMember.filter(clan__clan_id__in=clan_ids).prefetch_related( + "member", "clan" + ) async def get_clan_members_by_guild_id(self, guild_id): - return await ClanMember.filter(clan__guild__guild_id=guild_id).prefetch_related('member', 'clan', 'clan__guild') + return await ClanMember.filter(clan__guild__guild_id=guild_id).prefetch_related( + "member", "clan", "clan__guild" + ) async def get_clan_member_by_platform(self, member_id, platform_id, clan_ids): if platform_id == constants.PLATFORM_PSN: @@ -150,26 +180,24 @@ async def get_clan_member_by_platform(self, member_id, platform_id, clan_ids): query = ClanMember.get(clan_id__in=clan_ids, member__steam_id=member_id) elif platform_id == constants.PLATFORM_STADIA: query = ClanMember.get(clan_id__in=clan_ids, member__stadia_id=member_id) - return await query.prefetch_related('member') + return await query.prefetch_related("member") async def get_clans_by_guild(self, guild_id): - return await Clan.filter(guild__guild_id=guild_id).prefetch_related('guild') + return await Clan.filter(guild__guild_id=guild_id).prefetch_related("guild") async def get_clan_members_active(self, clan_db, **kwargs): if not kwargs: kwargs = dict(hours=1) return await ClanMember.filter( - last_active__gt=timezone.now() - timedelta(**kwargs), - clan=clan_db - ).prefetch_related('member') + last_active__gt=timezone.now() - timedelta(**kwargs), clan=clan_db + ).prefetch_related("member") async def get_clan_members_inactive(self, clan_db, **kwargs): if not kwargs: kwargs = dict(days=30) return await ClanMember.filter( - last_active__lt=timezone.now() - timedelta(**kwargs), - clan=clan_db - ).prefetch_related('member') + last_active__lt=timezone.now() - timedelta(**kwargs), clan=clan_db + ).prefetch_related("member") async def create_game(self, game): game_db = await Game.create(**vars(game)) @@ -198,7 +226,7 @@ async def create_game_member(self, player, game_db, clan_id, player_db=None): member=player_db, game=game_db, completed=player.completed, - time_played=player.time_played + time_played=player.time_played, ) except IntegrityError: # If one already exists, we can assume this is due to a drop/re-join event so @@ -206,11 +234,16 @@ async def create_game_member(self, player, game_db, clan_id, player_db=None): game_member_db = await GameMember.get(game=game_db, member=player_db) game_member_db.time_played += player.time_played - if not game_member_db.completed or game_member_db.completed != player.completed: + if ( + not game_member_db.completed + or game_member_db.completed != player.completed + ): game_member_db.completed = player.completed await game_member_db.save() - log.info(f"Player {member_hash(player)} created in game id {game_db.instance_id}") + log.info( + f"Player {member_hash(player)} created in game id {game_db.instance_id}" + ) async def close(self): await Tortoise.close_connections() diff --git a/seraphsix/errors.py b/seraphsix/errors.py index 42b13cf..5481854 100644 --- a/seraphsix/errors.py +++ b/seraphsix/errors.py @@ -3,9 +3,7 @@ class InvalidGameModeError(CommandError): def __init__(self, game_mode, supported_game_modes, *args): - message = ( - f"Invalid game mode `{game_mode}`, supported are `{', '.join(supported_game_modes)}`." - ) + message = f"Invalid game mode `{game_mode}`, supported are `{', '.join(supported_game_modes)}`." super().__init__(message, *args) @@ -23,7 +21,7 @@ def __init__(self, *args): class NotRegisteredError(CommandError): def __init__(self, prefix, *args): - message = (f"You don't seem to be registered, try running `{prefix}register`.") + message = f"You don't seem to be registered, try running `{prefix}register`." super().__init__(message, *args) diff --git a/seraphsix/models/__init__.py b/seraphsix/models/__init__.py index eb5d589..ae111aa 100644 --- a/seraphsix/models/__init__.py +++ b/seraphsix/models/__init__.py @@ -9,22 +9,25 @@ def encode_data(obj): if isinstance(obj, datetime): - obj = {'__datetime__': True, 'as_str': obj.strftime(constants.DESTINY_DATE_FORMAT)} + obj = { + "__datetime__": True, + "as_str": obj.strftime(constants.DESTINY_DATE_FORMAT), + } else: try: data = obj.to_dict() except AttributeError: pass else: - obj = {'__destiny_dataclass__': type(obj).__name__, 'as_str': data} + obj = {"__destiny_dataclass__": type(obj).__name__, "as_str": data} return obj def decode_data(obj): - if '__datetime__' in obj: - obj = datetime.strptime(obj['as_str'], constants.DESTINY_DATE_FORMAT) - elif '__destiny_dataclass__' in obj: - obj = eval(obj['__destiny_dataclass__']).from_dict(obj['as_str']) + if "__datetime__" in obj: + obj = datetime.strptime(obj["as_str"], constants.DESTINY_DATE_FORMAT) + elif "__destiny_dataclass__" in obj: + obj = eval(obj["__destiny_dataclass__"]).from_dict(obj["as_str"]) return obj diff --git a/seraphsix/models/database.py b/seraphsix/models/database.py index 9326341..4e88b72 100644 --- a/seraphsix/models/database.py +++ b/seraphsix/models/database.py @@ -3,8 +3,15 @@ from tortoise.contrib.postgres.indexes import PostgreSQLIndex from tortoise.exceptions import ValidationError from tortoise.fields import ( - BigIntField, IntField, CharField, BooleanField, ForeignKeyRelation, ForeignKeyField, DatetimeField, - FloatField, ReverseRelation + BigIntField, + IntField, + CharField, + BooleanField, + ForeignKeyRelation, + ForeignKeyField, + DatetimeField, + FloatField, + ReverseRelation, ) from tortoise.models import Model from tortoise.validators import Validator @@ -52,13 +59,23 @@ class Member(Model): is_cross_save = BooleanField(default=False) primary_membership_id = BigIntField(unique=True, null=True) - clan: ReverseRelation['ClanMember'] - games: ReverseRelation['GameMember'] + clan: ReverseRelation["ClanMember"] + games: ReverseRelation["GameMember"] class Meta: indexes = [ - PostgreSQLIndex(fields={'discord_id', 'bungie_id', 'xbox_id', 'psn_id', 'blizzard_id', - 'steam_id', 'stadia_id', 'the100_id'}) + PostgreSQLIndex( + fields={ + "discord_id", + "bungie_id", + "xbox_id", + "psn_id", + "blizzard_id", + "steam_id", + "stadia_id", + "the100_id", + } + ) ] # TODO: Figure out how to migrate this to Tortoise @@ -73,7 +90,7 @@ class Meta: class Guild(Model): guild_id = BigIntField(unique=True) - prefix = CharField(max_length=5, null=True, default='?') + prefix = CharField(max_length=5, null=True, default="?") clear_spam = BooleanField(default=False) aggregate_clans = BooleanField(default=True) track_sherpas = BooleanField(default=False) @@ -90,10 +107,10 @@ class Clan(Model): activity_tracking = BooleanField(default=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( - 'seraphsix.Guild', related_name='clans', to_field='id' + "seraphsix.Guild", related_name="clans", to_field="id" ) - members: ReverseRelation['ClanMember'] + members: ReverseRelation["ClanMember"] class ClanMember(Model): @@ -105,11 +122,11 @@ class ClanMember(Model): member_type = IntField(null=True, validators=[ClanMemberRankValidator()]) clan: ForeignKeyRelation[Clan] = ForeignKeyField( - 'seraphsix.Clan', related_name='members', to_field='id' + "seraphsix.Clan", related_name="members", to_field="id" ) member: ForeignKeyRelation[Member] = ForeignKeyField( - 'seraphsix.Member', related_name='clans', to_field='id' + "seraphsix.Member", related_name="clans", to_field="id" ) @@ -118,15 +135,17 @@ class ClanMemberApplication(Model): message_id = BigIntField(unique=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( - 'seraphsix.Guild', related_name='clanmemberapplications', to_field='id' + "seraphsix.Guild", related_name="clanmemberapplications", to_field="id" ) member: ForeignKeyRelation[Member] = ForeignKeyField( - 'seraphsix.Member', related_name='clanmemberapplications_created', to_field='id' + "seraphsix.Member", related_name="clanmemberapplications_created", to_field="id" ) approved_by: ForeignKeyRelation[Member] = ForeignKeyField( - 'seraphsix.Member', related_name='clanmemberapplications_approved', to_field='id' + "seraphsix.Member", + related_name="clanmemberapplications_approved", + to_field="id", ) @@ -137,20 +156,20 @@ class Game(Model): reference_id = BigIntField(null=True) class Meta: - indexes = ('mode_id', 'reference_id') + indexes = ("mode_id", "reference_id") class ClanGame(Model): clan: ForeignKeyRelation[Clan] = ForeignKeyField( - 'seraphsix.Clan', related_name='games', to_field='id' + "seraphsix.Clan", related_name="games", to_field="id" ) game: ForeignKeyRelation[Game] = ForeignKeyField( - 'seraphsix.Game', related_name='clans', to_field='id' + "seraphsix.Game", related_name="clans", to_field="id" ) class Meta: - indexes = ('clan', 'game') + indexes = ("clan", "game") class GameMember(Model): @@ -158,15 +177,15 @@ class GameMember(Model): completed = BooleanField(null=True) member: ForeignKeyRelation[Member] = ForeignKeyField( - 'seraphsix.Member', related_name='games', to_field='id' + "seraphsix.Member", related_name="games", to_field="id" ) game: ForeignKeyRelation[Game] = ForeignKeyField( - 'seraphsix.Game', related_name='members', to_field='id' + "seraphsix.Game", related_name="members", to_field="id" ) class Meta: - indexes = ('member', 'game') + indexes = ("member", "game") class TwitterChannel(Model): @@ -175,7 +194,7 @@ class TwitterChannel(Model): guild_id = BigIntField() class Meta: - indexes = ('channel_id', 'twitter_id', 'guild_id') + indexes = ("channel_id", "twitter_id", "guild_id") class Role(Model): @@ -188,14 +207,22 @@ class Role(Model): is_protected_clanmember = BooleanField(null=True) guild: ForeignKeyRelation[Guild] = ForeignKeyField( - 'seraphsix.Guild', related_name='roles', to_field='id' + "seraphsix.Guild", related_name="roles", to_field="id" ) class Meta: - indexes = ('guild', 'role_id') + indexes = ("guild", "role_id") __models__ = [ - Member, Guild, Clan, ClanMember, ClanMemberApplication, Game, ClanGame, GameMember, - TwitterChannel, Role + Member, + Guild, + Clan, + ClanMember, + ClanMemberApplication, + Game, + ClanGame, + GameMember, + TwitterChannel, + Role, ] diff --git a/seraphsix/models/destiny.py b/seraphsix/models/destiny.py index 93b7d4e..f678b35 100644 --- a/seraphsix/models/destiny.py +++ b/seraphsix/models/destiny.py @@ -8,19 +8,55 @@ from seraphsix.tasks.parsing import member_hash, member_hash_db __all__ = [ - 'DestinyActivity', 'DestinyActivityDetails', 'DestinyActivityResponse', 'DestinyActivityResults', - 'DestinyActivityStat', 'DestinyActivityStatValue', 'DestinyBungieNetUser', 'DestinyBungieNetUserInfo', - 'DestinyCharacter', 'DestinyCharacterData', 'DestinyCharacterResponse', 'DestinyCharacterResults', - 'DestinyGroup', 'DestinyGroupClanBannerData', 'DestinyGroupClanInfo', 'DestinyGroupD2ClanProgression', - 'DestinyGroupDetail', 'DestinyGroupFeatures', 'DestinyGroupMember', 'DestinyGroupMemberKick', - 'DestinyGroupMemberKickResponse', 'DestinyGroupMemberResults', 'DestinyGroupMembersResponse', - 'DestinyGroupPendingMember', 'DestinyGroupPendingMemberResults', 'DestinyGroupPendingMembersResponse', - 'DestinyGroupResponse', 'DestinyMemberGroup', 'DestinyMemberGroupResponse', 'DestinyMemberGroupResults', - 'DestinyMembership', 'DestinyMembershipResponse', 'DestinyMembershipResults', 'DestinyPGCR', - 'DestinyPGCREntry', 'DestinyPGCRExtended', 'DestinyPGCRResponse', 'DestinyPGCRWeapon', 'DestinyPlayer', - 'DestinyProfile', 'DestinyProfileData', 'DestinyProfileResponse', 'DestinyProfileResults', 'DestinyResponse', - 'DestinyResults', 'DestinySearchPlayerResponse', 'DestinyTokenErrorResponse', 'DestinyTokenResponse', - 'DestinyUserInfo', + "DestinyActivity", + "DestinyActivityDetails", + "DestinyActivityResponse", + "DestinyActivityResults", + "DestinyActivityStat", + "DestinyActivityStatValue", + "DestinyBungieNetUser", + "DestinyBungieNetUserInfo", + "DestinyCharacter", + "DestinyCharacterData", + "DestinyCharacterResponse", + "DestinyCharacterResults", + "DestinyGroup", + "DestinyGroupClanBannerData", + "DestinyGroupClanInfo", + "DestinyGroupD2ClanProgression", + "DestinyGroupDetail", + "DestinyGroupFeatures", + "DestinyGroupMember", + "DestinyGroupMemberKick", + "DestinyGroupMemberKickResponse", + "DestinyGroupMemberResults", + "DestinyGroupMembersResponse", + "DestinyGroupPendingMember", + "DestinyGroupPendingMemberResults", + "DestinyGroupPendingMembersResponse", + "DestinyGroupResponse", + "DestinyMemberGroup", + "DestinyMemberGroupResponse", + "DestinyMemberGroupResults", + "DestinyMembership", + "DestinyMembershipResponse", + "DestinyMembershipResults", + "DestinyPGCR", + "DestinyPGCREntry", + "DestinyPGCRExtended", + "DestinyPGCRResponse", + "DestinyPGCRWeapon", + "DestinyPlayer", + "DestinyProfile", + "DestinyProfileData", + "DestinyProfileResponse", + "DestinyProfileResults", + "DestinyResponse", + "DestinyResults", + "DestinySearchPlayerResponse", + "DestinyTokenErrorResponse", + "DestinyTokenResponse", + "DestinyUserInfo", ] @@ -64,17 +100,15 @@ class DestinyUserInfo: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) applicable_membership_types: Optional[List[int]] = None last_seen_display_name: Optional[str] = field( - metadata=config(field_name='LastSeenDisplayName'), - default=None + metadata=config(field_name="LastSeenDisplayName"), default=None ) last_seen_display_name_type: Optional[int] = field( - metadata=config(field_name='LastSeenDisplayNameType'), - default=None + metadata=config(field_name="LastSeenDisplayNameType"), default=None ) display_name: Optional[str] = None icon_path: Optional[str] = None @@ -92,7 +126,7 @@ class DestinyBungieNetUserInfo: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) display_name: str @@ -106,7 +140,7 @@ class DestinyProfileData: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) versions_owned: int @@ -136,7 +170,7 @@ class DestinyCharacterData: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) membership_type: int @@ -144,14 +178,14 @@ class DestinyCharacterData: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) date_last_played: datetime = field( metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) minutes_played_this_session: str @@ -185,7 +219,7 @@ class DestinyCharacter: @dataclass class DestinyActivityStatValue: value: float - display_value: str + display_value: str @dataclass_json(letter_case=LetterCase.CAMEL) @@ -204,7 +238,7 @@ class DestinyActivityDetails: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) mode: int @@ -220,7 +254,7 @@ class DestinyActivity: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) activity_details: DestinyActivityDetails @@ -270,7 +304,7 @@ class DestinyPGCREntry: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) values: Dict[str, DestinyActivityStat] @@ -284,7 +318,7 @@ class DestinyPGCR: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) activity_details: DestinyActivityDetails @@ -298,8 +332,12 @@ class DestinyPGCR: class DestinyMembership: # Basically the same as DestinyUserInfo with the addition of the "LastSeen" fields # TODO Consolidate these two? - last_seen_display_name: str = field(metadata=config(field_name='LastSeenDisplayName')) - last_seen_display_name_type: int = field(metadata=config(field_name='LastSeenDisplayNameType')) + last_seen_display_name: str = field( + metadata=config(field_name="LastSeenDisplayName") + ) + last_seen_display_name_type: int = field( + metadata=config(field_name="LastSeenDisplayNameType") + ) icon_path: str cross_save_override: int applicable_membership_types: List[int] @@ -309,7 +347,7 @@ class DestinyMembership: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) display_name: str @@ -322,7 +360,7 @@ class DestinyBungieNetUser: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) unique_name: str @@ -337,14 +375,14 @@ class DestinyBungieNetUser: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) last_update: datetime = field( metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) show_activity: bool @@ -359,7 +397,7 @@ class DestinyBungieNetUser: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) psn_display_name: Optional[str] = None @@ -380,9 +418,9 @@ class DestinyMembershipResults: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ), - default=None + default=None, ) @@ -395,14 +433,14 @@ class DestinyGroupMember: metadata=config( encoder=encode_datetime_timestamp, decoder=decode_datetime_timestamp, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) group_id: int = field( metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) destiny_user_info: DestinyUserInfo @@ -410,7 +448,7 @@ class DestinyGroupMember: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) bungie_net_user_info: Optional[DestinyBungieNetUserInfo] = None @@ -473,7 +511,7 @@ class DestinyGroupDetail: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) name: str @@ -482,21 +520,21 @@ class DestinyGroupDetail: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) creation_date: datetime = field( metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) modification_date: datetime = field( metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) about: str @@ -520,7 +558,7 @@ class DestinyGroupDetail: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) enable_invitation_messaging_for_admins: bool @@ -528,7 +566,7 @@ class DestinyGroupDetail: metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) features: DestinyGroupFeatures @@ -562,14 +600,14 @@ class DestinyGroupPendingMember: metadata=config( encoder=encode_id_string, decoder=decode_id_string, - mm_field=fields.Integer() + mm_field=fields.Integer(), ) ) creation_date: datetime = field( metadata=config( encoder=encode_datetime, decoder=decode_datetime, - mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT) + mm_field=fields.DateTime(format=constants.DESTINY_DATE_FORMAT), ) ) resolve_state: int @@ -587,12 +625,14 @@ class DestinyGroupMemberKick: @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyResponse: - error_code: int = field(metadata=config(field_name='ErrorCode')) - error_status: str = field(metadata=config(field_name='ErrorStatus')) - message: str = field(metadata=config(field_name='Message')) - message_data: object = field(metadata=config(field_name='MessageData')) - throttle_seconds: int = field(metadata=config(field_name='ThrottleSeconds')) - response: Optional[object] = field(metadata=config(field_name='Response'), default=None) + error_code: int = field(metadata=config(field_name="ErrorCode")) + error_status: str = field(metadata=config(field_name="ErrorStatus")) + message: str = field(metadata=config(field_name="Message")) + message_data: object = field(metadata=config(field_name="MessageData")) + throttle_seconds: int = field(metadata=config(field_name="ThrottleSeconds")) + response: Optional[object] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @@ -615,13 +655,17 @@ class DestinyMemberGroupResults(DestinyResults): @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyMemberGroupResponse(DestinyResponse): - response: Optional[DestinyMemberGroupResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyMemberGroupResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyGroupResponse(DestinyResponse): - response: Optional[DestinyGroup] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyGroup] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @@ -633,7 +677,9 @@ class DestinyGroupMemberResults(DestinyResults): @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyGroupMembersResponse(DestinyResponse): - response: Optional[DestinyGroupMemberResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyGroupMemberResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @@ -645,25 +691,33 @@ class DestinyGroupPendingMemberResults(DestinyResults): @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyGroupPendingMembersResponse(DestinyResponse): - response: Optional[DestinyGroupPendingMemberResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyGroupPendingMemberResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyGroupMemberKickResponse(DestinyResponse): - response: Optional[DestinyGroupMemberKick] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyGroupMemberKick] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinySearchPlayerResponse(DestinyResponse): - response: Optional[List[DestinyUserInfo]] = field(metadata=config(field_name='Response'), default=None) + response: Optional[List[DestinyUserInfo]] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyProfileResponse(DestinyResponse): - response: Optional[DestinyProfileResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyProfileResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @@ -675,25 +729,33 @@ class DestinyCharacterResults: @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyCharacterResponse(DestinyResponse): - response: Optional[DestinyCharacterResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyCharacterResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyActivityResponse(DestinyResponse): - response: Optional[DestinyActivityResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyActivityResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyMembershipResponse(DestinyResponse): - response: Optional[DestinyMembershipResults] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyMembershipResults] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json(letter_case=LetterCase.CAMEL) @dataclass class DestinyPGCRResponse(DestinyResponse): - response: Optional[DestinyPGCR] = field(metadata=config(field_name='Response'), default=None) + response: Optional[DestinyPGCR] = field( + metadata=config(field_name="Response"), default=None + ) @dataclass_json @@ -716,7 +778,6 @@ class DestinyTokenErrorResponse: class UserMembership(object): - def __init__(self): self.id = None self.username = None @@ -730,7 +791,6 @@ def __repr__(self): class User(object): - class Memberships(object): def __init__(self): self.bungie = UserMembership() @@ -745,22 +805,22 @@ def __init__(self, details): self.primary_membership_id = details.primary_membership_id self.is_cross_save = self.primary_membership_id is not None - if hasattr(details, 'destiny_user_info'): + if hasattr(details, "destiny_user_info"): self._process_membership(details.destiny_user_info) - elif hasattr(details, 'destiny_memberships'): + elif hasattr(details, "destiny_memberships"): for entry in details.destiny_memberships: self._process_membership(entry) - if hasattr(details, 'bungie_net_user_info'): + if hasattr(details, "bungie_net_user_info"): self._process_membership(details.bungie_net_user_info) - if hasattr(details, 'bungie_net_user'): + if hasattr(details, "bungie_net_user"): self._process_membership(details.bungie_net_user) def _process_membership(self, entry): - if not hasattr(entry, 'membership_id'): + if not hasattr(entry, "membership_id"): return - if not hasattr(entry, 'membership_type'): + if not hasattr(entry, "membership_type"): self.memberships.bungie(entry) else: if entry.membership_type == constants.PLATFORM_XBOX: @@ -791,12 +851,11 @@ def to_dict(self): stadia_id=self.memberships.stadia.id, stadia_username=self.memberships.stadia.username, primary_membership_id=self.primary_membership_id, - is_cross_save=self.is_cross_save + is_cross_save=self.is_cross_save, ) class Member(User): - def __init__(self, details, user_details): super().__init__(user_details) self.join_date = details.join_date @@ -835,11 +894,11 @@ def __init__(self, details): self.name = details.player.destiny_user_info.display_name self.completed = False - if details.values['completed'].basic.display_value == 'Yes': + if details.values["completed"].basic.display_value == "Yes": self.completed = True try: - self.time_played = details.values['timePlayedSeconds'].basic.value + self.time_played = details.values["timePlayedSeconds"].basic.value except KeyError: self.time_played = 0.0 @@ -875,7 +934,7 @@ def __str__(self): def __repr__(self): retval = self.__dict__ - retval['date'] = self.date.strftime(constants.DESTINY_DATE_FORMAT) + retval["date"] = self.date.strftime(constants.DESTINY_DATE_FORMAT) return str(retval) @@ -914,5 +973,8 @@ def __init__(self, details, member_dbs): self.clan_players = [] for player in self.players: player_hash = member_hash(player) - if player_hash in members.keys() and self.date > members[player_hash].join_date: + if ( + player_hash in members.keys() + and self.date > members[player_hash].join_date + ): self.clan_players.append(player) diff --git a/seraphsix/tasks/activity.py b/seraphsix/tasks/activity.py index 1ffce8e..1b841f6 100644 --- a/seraphsix/tasks/activity.py +++ b/seraphsix/tasks/activity.py @@ -11,25 +11,40 @@ from seraphsix.errors import PrivateHistoryError from seraphsix.models.database import ClanMember, Game, GameMember, Member from seraphsix.models.destiny import ( - Game as GameApi, ClanGame, DestinyProfileResponse, DestinyActivityResponse, DestinyPGCRResponse + Game as GameApi, + ClanGame, + DestinyProfileResponse, + DestinyActivityResponse, + DestinyPGCRResponse, +) +from seraphsix.tasks.core import ( + execute_pydest, + get_cached_members, + get_primary_membership, ) -from seraphsix.tasks.core import execute_pydest, get_cached_members, get_primary_membership from seraphsix.tasks.parsing import member_hash, member_hash_db log = logging.getLogger(__name__) -async def get_activity_history(ctx, platform_id, member_id, char_id, count=250, full_sync=False, mode=0): - destiny = ctx['destiny'] +async def get_activity_history( + ctx, platform_id, member_id, char_id, count=250, full_sync=False, mode=0 +): + destiny = ctx["destiny"] page = 0 activities = [] data = await execute_pydest( destiny.api.get_activity_history, - platform_id, member_id, char_id, count=count, page=page, mode=mode, - return_type=DestinyActivityResponse + platform_id, + member_id, + char_id, + count=count, + page=page, + mode=mode, + return_type=DestinyActivityResponse, ) if data.response: if full_sync: @@ -41,8 +56,13 @@ async def get_activity_history(ctx, platform_id, member_id, char_id, count=250, activities = data.response.activities data = await execute_pydest( destiny.api.get_activity_history, - platform_id, member_id, char_id, count=count, page=page, mode=mode, - return_type=DestinyActivityResponse + platform_id, + member_id, + char_id, + count=count, + page=page, + mode=mode, + return_type=DestinyActivityResponse, ) else: activities = data.response.activities @@ -50,28 +70,37 @@ async def get_activity_history(ctx, platform_id, member_id, char_id, count=250, activities = [] elif len(activities) == count: log.debug( - f'Activity count for {platform_id}-{member_id} ({char_id}) ' - f'equals {count} but full sync is disabled' + f"Activity count for {platform_id}-{member_id} ({char_id}) " + f"equals {count} but full sync is disabled" ) return activities async def get_pgcr(ctx, activity_id): - destiny = ctx['destiny'] + destiny = ctx["destiny"] data = await execute_pydest( - destiny.api.get_post_game_carnage_report, activity_id, return_type=DestinyPGCRResponse) + destiny.api.get_post_game_carnage_report, + activity_id, + return_type=DestinyPGCRResponse, + ) return data.response async def decode_activity(ctx, reference_id, definition): - destiny = ctx['destiny'] + destiny = ctx["destiny"] await execute_pydest(destiny.update_manifest, return_type=None) - return await execute_pydest(destiny.decode_hash, reference_id, definition, return_type=None) + return await execute_pydest( + destiny.decode_hash, reference_id, definition, return_type=None + ) -async def get_activity_list(ctx, platform_id, member_id, characters, count, full_sync=False, mode=0): +async def get_activity_list( + ctx, platform_id, member_id, characters, count, full_sync=False, mode=0 +): tasks = [ - get_activity_history(ctx, platform_id, member_id, character, count, full_sync, mode) + get_activity_history( + ctx, platform_id, member_id, character, count, full_sync, mode + ) for character in characters ] try: @@ -90,69 +119,90 @@ async def get_last_active(ctx, member_db=None, platform_id=None, member_id=None) platform_id, member_id, _ = get_primary_membership(member_db) profile = await execute_pydest( - ctx['destiny'].api.get_profile, platform_id, member_id, [constants.COMPONENT_PROFILES], - return_type=DestinyProfileResponse + ctx["destiny"].api.get_profile, + platform_id, + member_id, + [constants.COMPONENT_PROFILES], + return_type=DestinyProfileResponse, ) if not profile.response: - log.error(f"Could not get character data for {platform_id}-{member_id}: {profile.message}") + log.error( + f"Could not get character data for {platform_id}-{member_id}: {profile.message}" + ) else: acct_last_active = profile.response.profile.data.date_last_played - log.debug(f"Found last active date for {platform_id}-{member_id}: {acct_last_active}") + log.debug( + f"Found last active date for {platform_id}-{member_id}: {acct_last_active}" + ) return acct_last_active async def save_last_active(ctx, member_id): - clanmember_db = await ClanMember.get(member__id=member_id).prefetch_related("member") + clanmember_db = await ClanMember.get(member__id=member_id).prefetch_related( + "member" + ) last_active = await get_last_active(ctx, clanmember_db.member) clanmember_db.last_active = last_active await clanmember_db.save() async def store_last_active(ctx, guild_id, guild_name): - redis_jobs = ctx['redis_jobs'] + redis_jobs = ctx["redis_jobs"] for member_db in await get_cached_members(ctx, guild_id, guild_name): await redis_jobs.enqueue_job( - 'save_last_active', member_db.member.id, _job_id=f'save_last_active-{member_db.member.id}' + "save_last_active", + member_db.member.id, + _job_id=f"save_last_active-{member_db.member.id}", ) - log.info(f"Queued last active collection for all members of {guild_name} ({guild_id})") + log.info( + f"Queued last active collection for all members of {guild_name} ({guild_id})" + ) async def get_game_counts(database, game_mode, member_db=None): - base_query = Game.annotate(count=Count('id')) + base_query = Game.annotate(count=Count("id")) modes = [mode_id for mode_id in constants.SUPPORTED_GAME_MODES.get(game_mode)] if member_db: query = base_query.filter( members__member=member_db.member, members__member__clans__clan=member_db.clan, - mode_id__in=modes + mode_id__in=modes, ) else: query = base_query.filter(mode_id__in=modes) counts = {} - for row in await query.group_by('mode_id').values('mode_id', 'count'): - game_title = constants.MODE_MAP[row['mode_id']]['title'] - counts[game_title] = row['count'] + for row in await query.group_by("mode_id").values("mode_id", "count"): + game_title = constants.MODE_MAP[row["mode_id"]]["title"] + counts[game_title] = row["count"] return counts async def get_sherpa_time_played(member_db: object) -> Tuple[int, list]: await member_db.member - clan_sherpas = ClanMember.filter( - is_sherpa=True, id__not=member_db.id - ).only('member_id') + clan_sherpas = ClanMember.filter(is_sherpa=True, id__not=member_db.id).only( + "member_id" + ) full_list = list(constants.SUPPORTED_GAME_MODES.values()) mode_list = list(set([mode for sublist in full_list for mode in sublist])) all_games = GameMember.filter( - game__mode_id__in=mode_list, member=member_db.member, time_played__not_isnull=True - ).only('game_id') - - sherpa_games = GameMember.filter( - game_id__in=Subquery(all_games), member_id__in=Subquery(clan_sherpas), time_played__not_isnull=True - ).distinct().only('game_id') + game__mode_id__in=mode_list, + member=member_db.member, + time_played__not_isnull=True, + ).only("game_id") + + sherpa_games = ( + GameMember.filter( + game_id__in=Subquery(all_games), + member_id__in=Subquery(clan_sherpas), + time_played__not_isnull=True, + ) + .distinct() + .only("game_id") + ) # Ideally one more optimal query whould look like this, which would return all the data we'd need # in one query. But this is not currently possible with Tortoise. @@ -167,39 +217,54 @@ async def get_sherpa_time_played(member_db: object) -> Tuple[int, list]: # ) AS t # JOIN gamemember m ON m.game_id = t.game_id - query = await GameMember.annotate( - sherpa_time=Max('time_played') - ).filter( - game_id__in=Subquery(sherpa_games), - member_id__in=Subquery(clan_sherpas), - sherpa_time__not_isnull=True - ).group_by( - 'game_id' - ).values('game_id', 'sherpa_time') + query = ( + await GameMember.annotate(sherpa_time=Max("time_played")) + .filter( + game_id__in=Subquery(sherpa_games), + member_id__in=Subquery(clan_sherpas), + sherpa_time__not_isnull=True, + ) + .group_by("game_id") + .values("game_id", "sherpa_time") + ) total_time = 0 for result in query: - total_time += result['sherpa_time'] + total_time += result["sherpa_time"] # https://github.com/tortoise/tortoise-orm/issues/780 all_games = GameMember.filter( - game__mode_id__in=mode_list, member=member_db.member, time_played__not_isnull=True - ).only('game_id') - - sherpa_games = GameMember.filter( - game_id__in=Subquery(all_games), member_id__in=Subquery(clan_sherpas), time_played__not_isnull=True - ).distinct().only('game_id') + game__mode_id__in=mode_list, + member=member_db.member, + time_played__not_isnull=True, + ).only("game_id") + + sherpa_games = ( + GameMember.filter( + game_id__in=Subquery(all_games), + member_id__in=Subquery(clan_sherpas), + time_played__not_isnull=True, + ) + .distinct() + .only("game_id") + ) - sherpa_ids = GameMember.filter( - game_id__in=Subquery(sherpa_games), member_id__in=Subquery(clan_sherpas), time_played__not_isnull=True - ).distinct().values_list('member__discord_id', flat=True) + sherpa_ids = ( + GameMember.filter( + game_id__in=Subquery(sherpa_games), + member_id__in=Subquery(clan_sherpas), + time_played__not_isnull=True, + ) + .distinct() + .values_list("member__discord_id", flat=True) + ) return (total_time, sherpa_ids) async def store_all_games(ctx, guild_id, guild_name, count=30, recent=True): - database = ctx['database'] - redis_jobs = ctx['redis_jobs'] + database = ctx["database"] + redis_jobs = ctx["redis_jobs"] try: clan_dbs = await database.get_clans_by_guild(guild_id) @@ -209,27 +274,43 @@ async def store_all_games(ctx, guild_id, guild_name, count=30, recent=True): tasks = [] if recent: - log.info(f"Finding all games for members of {guild_name} ({guild_id}) active in the last hour") + log.info( + f"Finding all games for members of {guild_name} ({guild_id}) active in the last hour" + ) for clan_db in clan_dbs: if not clan_db.activity_tracking: - log.info(f"Clan activity tracking disabled for Clan {clan_db.name}, skipping") + log.info( + f"Clan activity tracking disabled for Clan {clan_db.name}, skipping" + ) continue - tasks.extend([ - get_member_activity(ctx, clanmember.member, count=count, full_sync=False) - for clanmember in await database.get_clan_members_active(clan_db, hours=1) - ]) + tasks.extend( + [ + get_member_activity( + ctx, clanmember.member, count=count, full_sync=False + ) + for clanmember in await database.get_clan_members_active( + clan_db, hours=1 + ) + ] + ) else: for clan_db in clan_dbs: if not clan_db.activity_tracking: - log.info(f"Clan activity tracking disabled for Clan {clan_db.name}, skipping") + log.info( + f"Clan activity tracking disabled for Clan {clan_db.name}, skipping" + ) continue - tasks.extend([ - get_member_activity(ctx, clanmember.member, count=count, full_sync=False) - for clanmember in await database.get_clan_members([clan_db.clan_id]) - ]) + tasks.extend( + [ + get_member_activity( + ctx, clanmember.member, count=count, full_sync=False + ) + for clanmember in await database.get_clan_members([clan_db.clan_id]) + ] + ) results = await asyncio.gather(*tasks) @@ -247,7 +328,11 @@ async def store_all_games(ctx, guild_id, guild_name, count=30, recent=True): for activity in unique_activities: activity_id = activity.activity_details.instance_id await redis_jobs.enqueue_job( - 'process_activity', activity, guild_id, guild_name, _job_id=f'process_activity-{activity_id}' + "process_activity", + activity, + guild_id, + guild_name, + _job_id=f"process_activity-{activity_id}", ) log.info( @@ -259,17 +344,24 @@ async def get_member_activity(ctx, member_db, count=250, full_sync=False, mode=0 platform_id, member_id, _ = get_primary_membership(member_db) characters = await get_characters(ctx, member_id, platform_id) if not characters: - log.error(f"Could not get character data for {platform_id}-{member_id} - {characters}") + log.error( + f"Could not get character data for {platform_id}-{member_id} - {characters}" + ) else: - return await get_activity_list(ctx, platform_id, member_id, characters, count, full_sync, mode) + return await get_activity_list( + ctx, platform_id, member_id, characters, count, full_sync, mode + ) async def get_characters(ctx, member_id, platform_id): - destiny = ctx['destiny'] + destiny = ctx["destiny"] retval = None profile = await execute_pydest( - destiny.api.get_profile, platform_id, member_id, [constants.COMPONENT_PROFILES], - return_type=DestinyProfileResponse + destiny.api.get_profile, + platform_id, + member_id, + [constants.COMPONENT_PROFILES], + return_type=DestinyProfileResponse, ) if profile.response: retval = profile.response.profile.data.character_ids @@ -277,7 +369,7 @@ async def get_characters(ctx, member_id, platform_id): async def process_activity(ctx, activity, guild_id, guild_name, player_check=False): - database = ctx['database'] + database = ctx["database"] game = GameApi(activity) member_dbs = await get_cached_members(ctx, guild_id, guild_name) @@ -286,42 +378,57 @@ async def process_activity(ctx, activity, guild_id, guild_name, player_check=Fal game_db = await Game.get_or_none(instance_id=game.instance_id) if not game_db: - log.debug(f"Skipping missing player check because game {game.instance_id} does not exist") + log.debug( + f"Skipping missing player check because game {game.instance_id} does not exist" + ) elif player_check: pgcr = await get_pgcr(ctx, game.instance_id) clan_game = ClanGame(pgcr, member_dbs) api_players_db = [ - await database.get_clan_member_by_platform(player.membership_id, player.membership_type, clan_ids) + await database.get_clan_member_by_platform( + player.membership_id, player.membership_type, clan_ids + ) for player in clan_game.clan_players ] db_players_db = await ClanMember.filter( member__games__game__instance_id=game.instance_id - ).prefetch_related('member') + ).prefetch_related("member") try: missing_player_dbs = set(api_players_db).symmetric_difference( - set([db_player for db_player in db_players_db])) + set([db_player for db_player in db_players_db]) + ) except TypeError: - log.debug(f"api_players_db: {api_players_db} db_players_db: {db_players_db}") + log.debug( + f"api_players_db: {api_players_db} db_players_db: {db_players_db}" + ) raise if len(missing_player_dbs) > 0: for missing_player_db in missing_player_dbs: member_db = missing_player_db.member for game_player in clan_game.clan_players: - if member_hash(game_player) == member_hash_db(member_db, game_player.membership_type) \ - and game.date > missing_player_db.join_date: - log.debug(f'Found missing player in {game.instance_id} {game_player}') + if ( + member_hash(game_player) + == member_hash_db(member_db, game_player.membership_type) + and game.date > missing_player_db.join_date + ): + log.debug( + f"Found missing player in {game.instance_id} {game_player}" + ) await database.create_game_member( - game_player, game_db, member_dbs[0].clan_id, member_db) + game_player, game_db, member_dbs[0].clan_id, member_db + ) else: log.debug(f"Continuing because game {game.instance_id} exists") return supported_modes = set(sum(constants.SUPPORTED_GAME_MODES.values(), [])) if game.mode_id not in supported_modes: - log.debug(f'Continuing because game {game.instance_id} mode {game.mode_id} not supported') + log.debug( + f"Continuing because game {game.instance_id} mode {game.mode_id} not supported" + ) return pgcr = await get_pgcr(ctx, game.instance_id) @@ -331,8 +438,10 @@ async def process_activity(ctx, activity, guild_id, guild_name, player_check=Fal clan_game = ClanGame(pgcr, member_dbs) game_mode_details = constants.MODE_MAP[game.mode_id] - if len(clan_game.clan_players) < game_mode_details['threshold']: - log.debug(f"Continuing because not enough clan players in game {game.instance_id}") + if len(clan_game.clan_players) < game_mode_details["threshold"]: + log.debug( + f"Continuing because not enough clan players in game {game.instance_id}" + ) return game_db = await database.create_game(clan_game) @@ -341,12 +450,14 @@ async def process_activity(ctx, activity, guild_id, guild_name, player_check=Fal return await database.create_clan_game(game_db, clan_game, clan_game.clan_id) - game_title = game_mode_details['title'].title() + game_title = game_mode_details["title"].title() log.info(f"{game_title} game id {game.instance_id} on {game.date} created") -async def store_member_history(ctx, member_db_id, guild_id, guild_name, full_sync=False, count=250, mode=0): - redis_jobs = ctx['redis_jobs'] +async def store_member_history( + ctx, member_db_id, guild_id, guild_name, full_sync=False, count=250, mode=0 +): + redis_jobs = ctx["redis_jobs"] member_db = await Member.get(id=member_db_id) activities = await get_member_activity(ctx, member_db, count, full_sync, mode) @@ -354,5 +465,9 @@ async def store_member_history(ctx, member_db_id, guild_id, guild_name, full_syn for activity in activities: activity_id = activity.activity_details.instance_id await redis_jobs.enqueue_job( - 'process_activity', activity, guild_id, guild_name, _job_id=f'process_activity-{activity_id}' + "process_activity", + activity, + guild_id, + guild_name, + _job_id=f"process_activity-{activity_id}", ) diff --git a/seraphsix/tasks/clan.py b/seraphsix/tasks/clan.py index 6e38027..949b84d 100644 --- a/seraphsix/tasks/clan.py +++ b/seraphsix/tasks/clan.py @@ -4,11 +4,23 @@ from seraphsix import constants from seraphsix.cogs.utils.message_manager import MessageManager from seraphsix.errors import InvalidAdminError -from seraphsix.models.database import Member as MemberDb, ClanMember, Clan, ClanMemberApplication +from seraphsix.models.database import ( + Member as MemberDb, + ClanMember, + Clan, + ClanMemberApplication, +) from seraphsix.models.destiny import ( - Member, DestinyGroupMembersResponse, DestinyMembershipResponse, DestinyGroupResponse + Member, + DestinyGroupMembersResponse, + DestinyMembershipResponse, + DestinyGroupResponse, +) +from seraphsix.tasks.core import ( + execute_pydest, + execute_pydest_auth, + get_primary_membership, ) -from seraphsix.tasks.core import execute_pydest, execute_pydest_auth, get_primary_membership log = logging.getLogger(__name__) @@ -16,7 +28,7 @@ async def sort_members(database, member_list): return_list = [] for member_hash in member_list: - _, platform_id, member_id = map(int, member_hash.split('-')) + _, platform_id, member_id = map(int, member_hash.split("-")) member_db = await database.get_member_by_platform(member_id, platform_id) if platform_id == constants.PLATFORM_XBOX: @@ -37,20 +49,26 @@ async def sort_members(database, member_list): async def get_all_members(destiny, group_id): group = await execute_pydest( - destiny.api.get_members_of_group, group_id, return_type=DestinyGroupMembersResponse) + destiny.api.get_members_of_group, + group_id, + return_type=DestinyGroupMembersResponse, + ) group_members = group.response.results for member in group_members: profile = await execute_pydest( - destiny.api.get_membership_data_by_id, member.destiny_user_info.membership_id, - return_type=DestinyMembershipResponse + destiny.api.get_membership_data_by_id, + member.destiny_user_info.membership_id, + return_type=DestinyMembershipResponse, ) yield Member(member, profile.response) async def get_bungie_members(destiny, clan_id): members = {} - async for member in get_all_members(destiny, clan_id): # pylint: disable=not-an-iterable - members[f'{clan_id}-{member}'] = member + async for member in get_all_members( + destiny, clan_id + ): # pylint: disable=not-an-iterable + members[f"{clan_id}-{member}"] = member return members @@ -67,16 +85,16 @@ async def get_database_members(database, clan_id): member_id = clanmember.member.steam_id elif clanmember.platform_id == constants.PLATFORM_STADIA: member_id = clanmember.member.stadia_id - member_hash = f'{clan_id}-{clanmember.platform_id}-{member_id}' + member_hash = f"{clan_id}-{clanmember.platform_id}-{member_id}" members[member_hash] = clanmember return members async def member_sync(ctx, guild_id, guild_name): - clan_dbs = await ctx['database'].get_clans_by_guild(guild_id) + clan_dbs = await ctx["database"].get_clans_by_guild(guild_id) member_changes = {} for clan_db in clan_dbs: - member_changes[clan_db.clan_id] = {'added': [], 'removed': [], 'changed': []} + member_changes[clan_db.clan_id] = {"added": [], "removed": [], "changed": []} bungie_members = {} db_members = {} @@ -87,34 +105,31 @@ async def member_sync(ctx, guild_id, guild_name): # Generate a dict of all members from both Bungie and the database for clan_db in clan_dbs: clan_id = clan_db.clan_id - bungie_tasks.append(get_bungie_members(ctx['destiny'], clan_id)) - db_tasks.append(get_database_members(ctx['database'], clan_id)) + bungie_tasks.append(get_bungie_members(ctx["destiny"], clan_id)) + db_tasks.append(get_database_members(ctx["database"], clan_id)) results = await asyncio.gather(*bungie_tasks, *db_tasks) # All Bungie results would be in the first half of the results - for result in results[:len(results)//2]: + for result in results[: len(results) // 2]: bungie_members.update(result) # All database results are in the second half - for result in results[len(results)//2:]: + # pylama:ignore=E203 + for result in results[len(results) // 2 :]: db_members.update(result) - bungie_member_set = set( - [member for member in bungie_members.keys()] - ) + bungie_member_set = set([member for member in bungie_members.keys()]) - db_member_set = set( - [member for member in db_members.keys()] - ) + db_member_set = set([member for member in db_members.keys()]) # Figure out if there are any members to add member_added_dbs = [] members_added = bungie_member_set - db_member_set for member_hash in members_added: member_info = bungie_members[member_hash] - clan_id, platform_id, member_id = map(int, member_hash.split('-')) - member_db = await ctx['database'].get_member_by_platform(member_id, platform_id) + clan_id, platform_id, member_id = map(int, member_hash.split("-")) + member_db = await ctx["database"].get_member_by_platform(member_id, platform_id) if not member_db: member_db = await MemberDb.create(**member_info.to_dict()) @@ -124,68 +139,88 @@ async def member_sync(ctx, guild_id, guild_name): platform_id=member_info.platform_id, is_active=True, member_type=member_info.member_type, - last_active=member_info.last_online_status_change + last_active=member_info.last_online_status_change, ) - await ClanMember.create( - clan=clan_db, member=member_db, **member_details - ) + await ClanMember.create(clan=clan_db, member=member_db, **member_details) member_added_dbs.append(member_db) - member_changes[clan_db.clan_id]['added'].append(member_hash) + member_changes[clan_db.clan_id]["added"].append(member_hash) # Figure out if there are any members to remove members_removed = db_member_set - bungie_member_set for member_hash in members_removed: - clan_id, platform_id, member_id = map(int, member_hash.split('-')) - member_db = await ctx['database'].get_member_by_platform(member_id, platform_id) + clan_id, platform_id, member_id = map(int, member_hash.split("-")) + member_db = await ctx["database"].get_member_by_platform(member_id, platform_id) if not member_db: continue await ClanMember.filter(member=member_db).delete() - member_changes[clan_db.clan_id]['removed'].append(member_hash) + member_changes[clan_db.clan_id]["removed"].append(member_hash) # Ensure we bust the member cache before queueing jobs # TODO: Until Tortoise has deserialization support, this has to stay disabled # await set_cached_members(ctx, guild_id, guild_name) for clan_id, changes in member_changes.items(): - if len(changes['added']): + if len(changes["added"]): # Kick off activity scans for each of the added members for member_db in member_added_dbs: - await ctx['redis_jobs'].enqueue_job( - 'store_member_history', member_db.id, guild_id, guild_name, full_sync=True, - _job_id=f'store_member_history-{member_db.id}') - - changes['added'] = await sort_members(ctx['database'], changes['added']) + await ctx["redis_jobs"].enqueue_job( + "store_member_history", + member_db.id, + guild_id, + guild_name, + full_sync=True, + _job_id=f"store_member_history-{member_db.id}", + ) + + changes["added"] = await sort_members(ctx["database"], changes["added"]) log.info(f"Added members {changes['added']} to clan id {clan_id}") - if len(changes['removed']): - changes['removed'] = await sort_members(ctx['database'], changes['removed']) + if len(changes["removed"]): + changes["removed"] = await sort_members(ctx["database"], changes["removed"]) log.info(f"Removed members {changes['removed']} from clan id {clan_id}") return member_changes async def info_sync(ctx, guild_id): - clan_dbs = await ctx['database'].get_clans_by_guild(guild_id) + clan_dbs = await ctx["database"].get_clans_by_guild(guild_id) clan_changes = {} for clan_db in clan_dbs: - group = await execute_pydest(ctx['destiny'].api.get_group, clan_db.clan_id, return_type=DestinyGroupResponse) + group = await execute_pydest( + ctx["destiny"].api.get_group, + clan_db.clan_id, + return_type=DestinyGroupResponse, + ) bungie_name = group.response.detail.name bungie_callsign = group.response.detail.clan_info.clan_callsign original_name = clan_db.name original_callsign = clan_db.callsign if clan_db.name != bungie_name: - clan_changes[clan_db.clan_id] = {'name': {'from': original_name, 'to': bungie_name}} + clan_changes[clan_db.clan_id] = { + "name": {"from": original_name, "to": bungie_name} + } clan_db.name = bungie_name if clan_db.callsign != bungie_callsign: if not clan_changes.get(clan_db.clan_id): clan_changes.update( - {clan_db.clan_id: {'callsign': {'from': original_callsign, 'to': bungie_callsign}}}) + { + clan_db.clan_id: { + "callsign": { + "from": original_callsign, + "to": bungie_callsign, + } + } + } + ) else: - clan_changes[clan_db.clan_id]['callsign'] = {'from': original_callsign, 'to': bungie_callsign} + clan_changes[clan_db.clan_id]["callsign"] = { + "from": original_callsign, + "to": bungie_callsign, + } clan_db.callsign = bungie_callsign await clan_db.save() diff --git a/seraphsix/tasks/config.py b/seraphsix/tasks/config.py index 515b8db..5282603 100644 --- a/seraphsix/tasks/config.py +++ b/seraphsix/tasks/config.py @@ -7,37 +7,47 @@ from get_docker_secret import get_docker_secret from pyrate_limiter import Duration, RequestRate, Limiter, RedisBucket from redis import ConnectionPool -from seraphsix.constants import LOG_FORMAT_MSG, DESTINY_DATE_FORMAT, DB_MAX_CONNECTIONS, ROOT_LOG_LEVEL +from seraphsix.constants import ( + LOG_FORMAT_MSG, + DESTINY_DATE_FORMAT, + DB_MAX_CONNECTIONS, + ROOT_LOG_LEVEL, +) def log_config(root_log_level: str = ROOT_LOG_LEVEL) -> dict: return { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'seraphsix': { - '()': 'seraphsix.utils.UTCFormatter', - 'fmt': LOG_FORMAT_MSG, - 'datefmt': DESTINY_DATE_FORMAT + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "seraphsix": { + "()": "seraphsix.utils.UTCFormatter", + "fmt": LOG_FORMAT_MSG, + "datefmt": DESTINY_DATE_FORMAT, } }, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'seraphsix' - } + "handlers": { + "console": {"class": "logging.StreamHandler", "formatter": "seraphsix"} + }, + "root": {"handlers": ["console"], "level": root_log_level}, + "loggers": { + "aiohttp.client": { + "handlers": ["console"], + "level": "ERROR", + "propagate": False, + }, + "aioredis": {"handlers": ["console"], "level": "INFO", "propagate": False}, + "arq": {"handlers": ["console"], "level": "INFO", "propagate": False}, + "backoff": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, + "bot": {"handlers": ["console"], "level": "DEBUG", "propagate": False}, + "discord": {"handlers": ["console"], "level": "INFO", "propagate": False}, + "pyrate_limiter": { + "handlers": ["console"], + "level": "ERROR", + "propagate": False, + }, + "db_client": {"handlers": ["console"], "level": "INFO", "propagate": False}, }, - 'root': {'handlers': ['console'], 'level': root_log_level}, - 'loggers': { - 'aiohttp.client': {'handlers': ['console'], 'level': 'ERROR', 'propagate': False}, - 'aioredis': {'handlers': ['console'], 'level': 'INFO', 'propagate': False}, - 'arq': {'handlers': ['console'], 'level': 'INFO', 'propagate': False}, - 'backoff': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - 'bot': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, - 'discord': {'handlers': ['console'], 'level': 'INFO', 'propagate': False}, - 'pyrate_limiter': {'handlers': ['console'], 'level': 'ERROR', 'propagate': False}, - 'db_client': {'handlers': ['console'], 'level': 'INFO', 'propagate': False} - } } @@ -49,10 +59,10 @@ class DestinyConfig: redirect_host: str def __init__(self): - self.api_key = get_docker_secret('bungie_api_key') - self.client_id = get_docker_secret('bungie_client_id') - self.client_secret = get_docker_secret('bungie_client_secret') - self.redirect_host = get_docker_secret('bungie_redirect_host') + self.api_key = get_docker_secret("bungie_api_key") + self.client_id = get_docker_secret("bungie_client_id") + self.client_secret = get_docker_secret("bungie_client_secret") + self.redirect_host = get_docker_secret("bungie_redirect_host") @dataclass @@ -61,8 +71,8 @@ class The100Config: base_url: str def __init__(self): - self.api_key = get_docker_secret('the100_api_key') - self.base_url = get_docker_secret('the100_api_url') + self.api_key = get_docker_secret("the100_api_key") + self.base_url = get_docker_secret("the100_api_url") @dataclass @@ -73,10 +83,10 @@ class TwitterConfig: access_token_secret: str def __init__(self): - self.consumer_key = get_docker_secret('twitter_consumer_key') - self.consumer_secret = get_docker_secret('twitter_consumer_secret') - self.access_token = get_docker_secret('twitter_access_token') - self.access_token_secret = get_docker_secret('twitter_access_token_secret') + self.consumer_key = get_docker_secret("twitter_consumer_key") + self.consumer_secret = get_docker_secret("twitter_consumer_secret") + self.access_token = get_docker_secret("twitter_access_token") + self.access_token_secret = get_docker_secret("twitter_access_token_secret") def asdict(self): return asdict(self) @@ -119,43 +129,55 @@ class Config(Borg): def __init__(self): Borg.__init__(self) - database_user = get_docker_secret('seraphsix_pg_db_user', default='seraphsix') - database_password = get_docker_secret('seraphsix_pg_db_pass') - database_host = get_docker_secret('seraphsix_pg_db_host', default='localhost') - database_port = get_docker_secret('seraphsix_pg_db_port', default='5432') - database_name = get_docker_secret('seraphsix_pg_db_name', default='seraphsix') - self.database_conns = get_docker_secret('seraphsix_pg_db_conns', default=DB_MAX_CONNECTIONS, cast_to=int) + database_user = get_docker_secret("seraphsix_pg_db_user", default="seraphsix") + database_password = get_docker_secret("seraphsix_pg_db_pass") + database_host = get_docker_secret("seraphsix_pg_db_host", default="localhost") + database_port = get_docker_secret("seraphsix_pg_db_port", default="5432") + database_name = get_docker_secret("seraphsix_pg_db_name", default="seraphsix") + self.database_conns = get_docker_secret( + "seraphsix_pg_db_conns", default=DB_MAX_CONNECTIONS, cast_to=int + ) database_auth = f"{database_user}:{database_password}" self.database_url = f"postgres://{database_auth}@{database_host}:{database_port}/{database_name}" - redis_password = get_docker_secret('seraphsix_redis_pass') - redis_host = get_docker_secret('seraphsix_redis_host', default='localhost') - redis_port = get_docker_secret('seraphsix_redis_port', default='6379') + redis_password = get_docker_secret("seraphsix_redis_pass") + redis_host = get_docker_secret("seraphsix_redis_host", default="localhost") + redis_port = get_docker_secret("seraphsix_redis_port", default="6379") self.redis_url = f"redis://:{redis_password}@{redis_host}:{redis_port}" - self.arq_redis = RedisSettings.from_dsn(f'{self.redis_url}/1') + self.arq_redis = RedisSettings.from_dsn(f"{self.redis_url}/1") self.destiny = DestinyConfig() self.the100 = The100Config() self.twitter = TwitterConfig() - self.discord_api_key = get_docker_secret('discord_api_key') - self.home_server = get_docker_secret('home_server', cast_to=int) - self.log_channel = get_docker_secret('home_server_log_channel', cast_to=int) - self.reg_channel = get_docker_secret('home_server_reg_channel', cast_to=int) - self.enable_activity_tracking = get_docker_secret('enable_activity_tracking', cast_to=bool) - - self.flask_app_key = os.environb[b'FLASK_APP_KEY'].decode('unicode-escape').encode('latin-1') - - self.activity_cutoff = get_docker_secret('activity_cutoff') + self.discord_api_key = get_docker_secret("discord_api_key") + self.home_server = get_docker_secret("home_server", cast_to=int) + self.log_channel = get_docker_secret("home_server_log_channel", cast_to=int) + self.reg_channel = get_docker_secret("home_server_reg_channel", cast_to=int) + self.enable_activity_tracking = get_docker_secret( + "enable_activity_tracking", cast_to=bool + ) + + self.flask_app_key = ( + os.environb[b"FLASK_APP_KEY"].decode("unicode-escape").encode("latin-1") + ) + + self.activity_cutoff = get_docker_secret("activity_cutoff") if self.activity_cutoff: - self.activity_cutoff = datetime.strptime(self.activity_cutoff, '%Y-%m-%d').astimezone(tz=pytz.utc) + self.activity_cutoff = datetime.strptime( + self.activity_cutoff, "%Y-%m-%d" + ).astimezone(tz=pytz.utc) - self.root_log_level = get_docker_secret('root_log_level', default=ROOT_LOG_LEVEL, cast_to=str) + self.root_log_level = get_docker_secret( + "root_log_level", default=ROOT_LOG_LEVEL, cast_to=str + ) bucket_kwargs = { "redis_pool": ConnectionPool.from_url(self.redis_url), - "bucket_name": "ratelimit" + "bucket_name": "ratelimit", } destiny_api_rate = RequestRate(20, Duration.SECOND) - self.destiny_api_limiter = Limiter(destiny_api_rate, bucket_class=RedisBucket, bucket_kwargs=bucket_kwargs) + self.destiny_api_limiter = Limiter( + destiny_api_rate, bucket_class=RedisBucket, bucket_kwargs=bucket_kwargs + ) diff --git a/seraphsix/tasks/core.py b/seraphsix/tasks/core.py index 40e75e8..2e5de7e 100644 --- a/seraphsix/tasks/core.py +++ b/seraphsix/tasks/core.py @@ -12,7 +12,11 @@ from seraphsix import constants from seraphsix.models import deserializer, serializer -from seraphsix.models.destiny import DestinyResponse, DestinyTokenResponse, DestinyTokenErrorResponse +from seraphsix.models.destiny import ( + DestinyResponse, + DestinyTokenResponse, + DestinyTokenErrorResponse, +) from seraphsix.tasks.config import Config from seraphsix.errors import MaintenanceError, PrivateHistoryError, InvalidCommandError @@ -24,40 +28,49 @@ async def create_redis_jobs_pool(): return await arq.create_pool( config.arq_redis, job_serializer=lambda b: serializer(b), - job_deserializer=lambda b: deserializer(b) + job_deserializer=lambda b: deserializer(b), ) async def queue_redis_job(ctx, message, *args, **kwargs): log.info(f"Queueing task to {message}") - await ctx['redis_jobs'].enqueue_job(*args, **kwargs) + await ctx["redis_jobs"].enqueue_job(*args, **kwargs) def backoff_handler(details): - if details['wait'] > 30 or details['tries'] > 10: + if details["wait"] > 30 or details["tries"] > 10: log.debug( f"Backing off {details['wait']:0.1f} seconds after {details['tries']} tries " f"for {details['target']} args {details['args']} kwargs {details['kwargs']}" ) -@backoff.on_exception(backoff.constant, (PrivateHistoryError, MaintenanceError), max_tries=1, logger=None) +@backoff.on_exception( + backoff.constant, (PrivateHistoryError, MaintenanceError), max_tries=1, logger=None +) @backoff.on_exception( backoff.expo, - (PydestException, asyncio.TimeoutError, BucketFullException, ServerDisconnectedError, ClientOSError), - logger=None, on_backoff=backoff_handler + ( + PydestException, + asyncio.TimeoutError, + BucketFullException, + ServerDisconnectedError, + ClientOSError, + ), + logger=None, + on_backoff=backoff_handler, ) async def execute_pydest(function, *args, **kwargs): retval = None - if 'return_type' in kwargs: - return_type = kwargs.pop('return_type') + if "return_type" in kwargs: + return_type = kwargs.pop("return_type") else: return_type = DestinyResponse log.debug(f"{function} {args} {kwargs}") - async with config.destiny_api_limiter.ratelimit('destiny_api', delay=True): + async with config.destiny_api_limiter.ratelimit("destiny_api", delay=True): data = await function(*args, **kwargs) log.debug(f"{function} {args} {kwargs} - {data}") @@ -79,23 +92,29 @@ async def execute_pydest(function, *args, **kwargs): raise RuntimeError("Unexpected empty response from the Destiny API") # DestinyTokenResponse and DestinyTokenErrorResponse have an "error" field - if hasattr(res, 'error') and res.error: + if hasattr(res, "error") and res.error: log.error(f"Error running {function} {args} {kwargs} - {res}") raise RuntimeError(f"Error running {function} {args} {kwargs} - {res}") - elif hasattr(res, 'error_status') and res.error_status != 'Success': + elif hasattr(res, "error_status") and res.error_status != "Success": # The rest of the API responses use "error_status" # https://bungie-net.github.io/#/components/schemas/Exceptions.PlatformErrorCodes - if res.error_status == 'SystemDisabled': + if res.error_status == "SystemDisabled": raise MaintenanceError - elif res.error_status in ['PerEndpointRequestThrottleExceeded', 'DestinyDirectBabelClientTimeout']: + elif res.error_status in [ + "PerEndpointRequestThrottleExceeded", + "DestinyDirectBabelClientTimeout", + ]: raise PydestException - elif res.error_status == 'DestinyPrivacyRestriction': + elif res.error_status == "DestinyPrivacyRestriction": raise PrivateHistoryError - elif res.error_status == 'WebAuthRequired': + elif res.error_status == "WebAuthRequired": raise pydest.PydestTokenException else: log.error(f"Error running {function} {args} {kwargs} - {res}") - if res.error_status not in ['DestinyAccountNotFound', 'ClanMaximumMembershipReached']: + if res.error_status not in [ + "DestinyAccountNotFound", + "ClanMaximumMembershipReached", + ]: raise PydestException retval = res log.debug(f"{function} {args} {kwargs} - {res}") @@ -107,7 +126,7 @@ async def execute_pydest_auth(ctx, func, auth_user_db, manager, *args, **kwargs) res = await execute_pydest(func, *args, **kwargs) except pydest.PydestTokenException: tokens = await refresh_user_tokens(ctx, manager, auth_user_db) - kwargs['access_token'] = tokens.access_token + kwargs["access_token"] = tokens.access_token res = await execute_pydest(func, *args, **kwargs) auth_user_db.bungie_access_token = tokens.access_token auth_user_db.bungie_refresh_token = tokens.refresh_token @@ -117,21 +136,30 @@ async def execute_pydest_auth(ctx, func, auth_user_db, manager, *args, **kwargs) async def refresh_user_tokens(ctx, manager, auth_user_db): tokens = await execute_pydest( - ctx['destiny'].api.refresh_oauth_token, auth_user_db.bungie_refresh_token, - return_type=DestinyTokenResponse + ctx["destiny"].api.refresh_oauth_token, + auth_user_db.bungie_refresh_token, + return_type=DestinyTokenResponse, ) if tokens.error: log.warning(f"{tokens.error_description} Registration is needed") - user_info = await register(manager, "Your registration token has expired and re-registration is needed.") + user_info = await register( + manager, + "Your registration token has expired and re-registration is needed.", + ) if not user_info: - raise InvalidCommandError("I'm not sure where you went. We can try this again later.") - tokens = {token: user_info.get(token) for token in [tokens.access_token, tokens.refresh_token]} + raise InvalidCommandError( + "I'm not sure where you went. We can try this again later." + ) + tokens = { + token: user_info.get(token) + for token in [tokens.access_token, tokens.refresh_token] + } return tokens -async def register(manager, extra_message='', confirm_message=''): +async def register(manager, extra_message="", confirm_message=""): ctx = manager.ctx if not confirm_message: @@ -145,7 +173,7 @@ async def register(manager, extra_message='', confirm_message=''): await manager.send_message( f"{extra_message} Registration instructions have been sent directly to {ctx.author}".strip(), mention=False, - clean=False + clean=False, ) # Prompt user with link to Bungie.net OAuth authentication @@ -160,34 +188,35 @@ async def register(manager, extra_message='', confirm_message=''): registration_msg = await manager.send_private_embed(e) # Wait for user info from the web server via Redis - res = await ctx.bot.ext_conns['redis_cache'].subscribe(ctx.author.id) + res = await ctx.bot.ext_conns["redis_cache"].subscribe(ctx.author.id) tsk = asyncio.create_task(wait_for_msg(res[0])) try: user_info = await asyncio.wait_for(tsk, timeout=constants.TIME_MIN_SECONDS) except asyncio.TimeoutError: - log.debug(f"Timed out waiting for {str(ctx.author)} ({ctx.author.id}) to register") - await manager.send_private_message("I'm not sure where you went. We can try this again later.") + log.debug( + f"Timed out waiting for {str(ctx.author)} ({ctx.author.id}) to register" + ) + await manager.send_private_message( + "I'm not sure where you went. We can try this again later." + ) await registration_msg.delete() await manager.clean_messages() - await ctx.bot.ext_conns['redis_cache'].unsubscribe(ctx.author.id) + await ctx.bot.ext_conns["redis_cache"].unsubscribe(ctx.author.id) return (None, None) await ctx.author.dm_channel.trigger_typing() # Send confirmation of successful registration - e = discord.Embed( - colour=constants.BLUE, - title=confirm_message - ) + e = discord.Embed(colour=constants.BLUE, title=confirm_message) embed = await manager.send_private_embed(e) - await ctx.bot.ext_conns['redis_cache'].unsubscribe(ctx.author.id) + await ctx.bot.ext_conns["redis_cache"].unsubscribe(ctx.author.id) return embed, user_info async def wait_for_msg(channel): """Wait for a message on the specified Redis channel""" - while (await channel.wait_message()): + while await channel.wait_message(): pickled_msg = await channel.get() return pickle.loads(pickled_msg) @@ -202,7 +231,7 @@ async def wait_for_msg(channel): async def get_cached_members(ctx, guild_id, guild_name): - return await ctx['database'].get_clan_members_by_guild_id(guild_id) + return await ctx["database"].get_clan_members_by_guild_id(guild_id) # TODO: Until Tortoise has deserialization support, this has to stay disabled # cache_key = f'{guild_id}-members' # clan_members = await ctx['redis_cache'].get(cache_key) @@ -237,7 +266,7 @@ def get_primary_membership(member_db, restrict_platform_id=None): [constants.PLATFORM_XBOX, member_db.xbox_id, member_db.xbox_username], [constants.PLATFORM_PSN, member_db.psn_id, member_db.psn_username], [constants.PLATFORM_STEAM, member_db.steam_id, member_db.steam_username], - [constants.PLATFORM_STADIA, member_db.stadia_id, member_db.stadia_username] + [constants.PLATFORM_STADIA, member_db.stadia_id, member_db.stadia_username], ] membership_id = member_db.primary_membership_id platform_id = None @@ -246,8 +275,9 @@ def get_primary_membership(member_db, restrict_platform_id=None): platform_id = membership[0] username = membership[2] break - elif (restrict_platform_id and restrict_platform_id == membership[0]) or \ - (not membership_id and membership[1]): + elif (restrict_platform_id and restrict_platform_id == membership[0]) or ( + not membership_id and membership[1] + ): platform_id, membership_id, username = membership break @@ -262,7 +292,7 @@ def get_memberships(member_db): [constants.PLATFORM_XBOX, member_db.xbox_id, member_db.xbox_username], [constants.PLATFORM_PSN, member_db.psn_id, member_db.psn_username], [constants.PLATFORM_STEAM, member_db.steam_id, member_db.steam_username], - [constants.PLATFORM_STADIA, member_db.stadia_id, member_db.stadia_username] + [constants.PLATFORM_STADIA, member_db.stadia_id, member_db.stadia_username], ] retval = {} for membership in memberships: diff --git a/seraphsix/tasks/discord.py b/seraphsix/tasks/discord.py index 85ac3bc..0ed792e 100644 --- a/seraphsix/tasks/discord.py +++ b/seraphsix/tasks/discord.py @@ -30,7 +30,7 @@ async def store_sherpas(bot, guild): sherpas_db = await ClanMember.filter( is_sherpa=True, clan__guild_id=guild.id - ).prefetch_related('member') + ).prefetch_related("member") sherpas_db_ids = [sherpa_db.member.discord_id for sherpa_db in sherpas_db] discord_set = set(sherpas_discord_ids) @@ -46,7 +46,9 @@ async def store_sherpas(bot, guild): added = [sherpa async for sherpa in convert_sherpas(bot, sherpas_added)] message_added = [f"{str(sherpa)} {sherpa.id}" for sherpa in added] - log.info(f"Sherpas added in {str(discord_guild)} ({guild.guild_id}): {message_added}") + log.info( + f"Sherpas added in {str(discord_guild)} ({guild.guild_id}): {message_added}" + ) if sherpas_removed: members = ClanMember.filter(member__discord_id__in=sherpas_removed) @@ -54,7 +56,9 @@ async def store_sherpas(bot, guild): removed = [sherpa async for sherpa in convert_sherpas(bot, sherpas_removed)] message_removed = [f"{str(sherpa)} {sherpa.id}" for sherpa in removed] - log.info(f"Sherpas removed in {str(discord_guild)} ({guild.guild_id}): {message_removed}") + log.info( + f"Sherpas removed in {str(discord_guild)} ({guild.guild_id}): {message_removed}" + ) return (added, removed) @@ -79,9 +83,7 @@ async def update_sherpa(bot, before, after): f"in {str(after.guild)} ({after.guild.id})" ) - roles_db = await Role.filter( - guild__guild_id=after.guild.id, is_sherpa=True - ) + roles_db = await Role.filter(guild__guild_id=after.guild.id, is_sherpa=True) role_db_ids = set([role.role_id for role in roles_db]) member_db = await ClanMember.get_or_none(member__discord_id=after.id) diff --git a/seraphsix/tasks/parsing.py b/seraphsix/tasks/parsing.py index 9bb68e8..29caa31 100644 --- a/seraphsix/tasks/parsing.py +++ b/seraphsix/tasks/parsing.py @@ -2,12 +2,12 @@ def member_hash(member): - return f'{member.membership_type}-{member.membership_id}' + return f"{member.membership_type}-{member.membership_id}" def member_hash_db(member_db, platform_id): membership_id, _ = parse_platform(member_db, platform_id) - return f'{platform_id}-{membership_id}' + return f"{platform_id}-{membership_id}" def parse_platform(member_db, platform_id): diff --git a/seraphsix/tasks/the100.py b/seraphsix/tasks/the100.py index 32f26bf..550d3de 100644 --- a/seraphsix/tasks/the100.py +++ b/seraphsix/tasks/the100.py @@ -8,17 +8,17 @@ def collate_the100_activities(activities, game_name): game_activities = [] game_activities_by_id = {} for activity in activities: - key = activity['name'].strip() - val = activity['id'] + key = activity["name"].strip() + val = activity["id"] game_activities_by_id[val] = key # Sanitize the game names to ensure merging works correctly, raids require 'Fresh' to be appended, # other things require 'Normal', and one requires 'Anything' - if key in THE100_GAME_SORT_RULES[game_name]['fresh']: + if key in THE100_GAME_SORT_RULES[game_name]["fresh"]: key = f"{key} - Fresh" - elif key in THE100_GAME_SORT_RULES[game_name]['normal']: + elif key in THE100_GAME_SORT_RULES[game_name]["normal"]: key = f"{key} - Normal" - elif key in THE100_GAME_SORT_RULES[game_name]['anything']: + elif key in THE100_GAME_SORT_RULES[game_name]["anything"]: key = f"{key} - Anything" game_activities.append({key: val}) @@ -31,16 +31,21 @@ def collate_the100_activities(activities, game_name): # [{'Raid': {'Crown of Sorrow': {'Fresh': 1}}}, {'Raid': {'Last Wish': {'Fresh': 2}}}] game_activities_list = [] for game_activity in game_activities: - game_activities_list.append(reduce(lambda res, cur: {cur: res}, reversed( - list(game_activity.keys())[0].split(' - ')), list(game_activity.values())[0])) + game_activities_list.append( + reduce( + lambda res, cur: {cur: res}, + reversed(list(game_activity.keys())[0].split(" - ")), + list(game_activity.values())[0], + ) + ) # Merge the list of dictionaries into one big dictionary # {'Raid': {'Crown of Sorrow': {'Fresh': 1}}, {'Last Wish': {'Fresh': 2}}} - game_activities_dict = (reduce(merge_dicts, game_activities_list)) + game_activities_dict = reduce(merge_dicts, game_activities_list) # Sort the contents of the above into a new dict game_activities_merged = {} - for key in ['Raid', 'Crucible', 'Gambit', 'Strike', 'Quest']: + for key in ["Raid", "Crucible", "Gambit", "Strike", "Quest"]: unsorted = game_activities_dict.pop(key) try: game_activities_merged[key] = sort_dict(unsorted) @@ -48,6 +53,6 @@ def collate_the100_activities(activities, game_name): game_activities_merged[key] = unsorted # Anything left over in that dict is added under a single category - game_activities_merged['Other'] = sort_dict(game_activities_dict) + game_activities_merged["Other"] = sort_dict(game_activities_dict) return game_activities_merged, game_activities_by_id