From 223a95a206d605c7d8015249897e536e9d841809 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:07:32 +0100 Subject: [PATCH 01/19] migrate requirements into pyproject and fix up tox --- pyproject.toml | 31 +- requirements.txt | 24 - tox.ini | 6 +- uv.lock | 1326 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1356 insertions(+), 31 deletions(-) delete mode 100644 requirements.txt create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 753cf096..19acf6ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,32 @@ +[project] +name = "kfo-server" +version = "0.1.0" +description = "KFO Attorney Online Server" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "aiohttp>=3.13.2", + "arrow>=1.3.0", + "black", + "discord.py>=2.5.2", + "geoip2>=4.7.0", + "oyaml>=1.0", + "pystun3>=1.0.0", + "pytest", + "PyYAML>=6.0.1", + "requests>=2.31.0", + "timeparse-plus>=1.2.0", + "tox>=4", + "websockets>=12.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["server"] + [tool.black] line-length = 120 target-version = ["py311"] -# You can add patterns here if you need to exclude generated files, etc. -# extend-exclude = [] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d9593f29..00000000 --- a/requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -aiohttp==3.13.2 -aiosignal==1.4.0 -arrow==1.3.0 -async-timeout==4.0.3 -attrs==23.1.0 -certifi==2023.7.22 -charset-normalizer==3.3.2 -discord.py==2.5.2 -frozenlist==1.6.0 -geoip2==4.7.0 -idna==3.4 -maxminddb==2.5.1 -multidict==6.4.3 -oyaml==1.0 -pystun3==1.0.0 -python-dateutil==2.8.2 -PyYAML==6.0.1 -requests==2.31.0 -six==1.16.0 -timeparse-plus==1.2.0 -types-python-dateutil==2.8.19.14 -urllib3==2.1.0 -websockets==12.0 -yarl==1.20.0 diff --git a/tox.ini b/tox.ini index 23adbe8e..58331e49 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,8 @@ requires = tox>=4 skip_missing_interpreters = true [testenv] -# Do not try to build/install the project as a package; run tests against the checkout -package = skip +package = editable setenv = PYTHONPATH = {toxinidir} -deps = - -rrequirements.txt - pytest commands = pytest -q diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..1893927d --- /dev/null +++ b/uv.lock @@ -0,0 +1,1326 @@ +version = 1 +revision = 2 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "black" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, + { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, + { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, + { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, + { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "discord-py" +version = "2.6.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/e7/9b1dbb9b2fc07616132a526c05af23cfd420381793968a189ee08e12e35f/discord_py-2.6.4.tar.gz", hash = "sha256:44384920bae9b7a073df64ae9b14c8cf85f9274b5ad5d1d07bd5a67539de2da9", size = 1092623, upload-time = "2025-10-08T21:45:43.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/ae/3d3a89b06f005dc5fa8618528dde519b3ba7775c365750f7932b9831ef05/discord_py-2.6.4-py3-none-any.whl", hash = "sha256:2783b7fb7f8affa26847bfc025144652c294e8fe6e0f8877c67ed895749eb227", size = 1209284, upload-time = "2025-10-08T21:45:41.679Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "geoip2" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "maxminddb" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/70/3d9e87289f79713aaf0fea9df4aa8e68776640fe59beb6299bb214610cfd/geoip2-5.2.0.tar.gz", hash = "sha256:6c9ded1953f8eb16043ed0a8ea20e6e9524ea7b65eb745724e12490aca44ef00", size = 176498, upload-time = "2025-11-20T18:21:08.874Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/d2/d55df737199a52b9d06e742ed2a608c525f0677e40375951372e65714fbd/geoip2-5.2.0-py3-none-any.whl", hash = "sha256:3d1546fd4eb7cad20445d027d2d9e81d3a71c074e019383f30db5d45e2c23320", size = 28991, upload-time = "2025-11-20T18:21:07.178Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "kfo-server" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "arrow" }, + { name = "black" }, + { name = "discord-py" }, + { name = "geoip2" }, + { name = "oyaml" }, + { name = "pystun3" }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "timeparse-plus" }, + { name = "tox" }, + { name = "websockets" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.13.2" }, + { name = "arrow", specifier = ">=1.3.0" }, + { name = "black" }, + { name = "discord-py", specifier = ">=2.5.2" }, + { name = "geoip2", specifier = ">=4.7.0" }, + { name = "oyaml", specifier = ">=1.0" }, + { name = "pystun3", specifier = ">=1.0.0" }, + { name = "pytest" }, + { name = "pyyaml", specifier = ">=6.0.1" }, + { name = "requests", specifier = ">=2.31.0" }, + { name = "timeparse-plus", specifier = ">=1.2.0" }, + { name = "tox", specifier = ">=4" }, + { name = "websockets", specifier = ">=12.0" }, +] + +[[package]] +name = "maxminddb" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/6e/6adbb0b2280a853e8b3344737fea5167e8a2a2ff67168555589b7278e2e8/maxminddb-3.0.0.tar.gz", hash = "sha256:9792b19625945dff146e2e3187f9e470b82330a912f7cea5581b8bd5af30da8b", size = 199784, upload-time = "2025-10-15T20:50:07.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/15/a3c156c609b59342799fd1c0e60e4a6f6e096e2a18107d88b61a39c76ab5/maxminddb-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8e34a0cd9a67f446a6b425b857ac7b63254b5ce64d0b38e013d0e531588b7a66", size = 53857, upload-time = "2025-10-15T20:48:38.447Z" }, + { url = "https://files.pythonhosted.org/packages/fa/42/8e367bfa2a9c8ffbe0f0b60a904d35b7cb9d6a0d3a3c8fd803a932b3226e/maxminddb-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e1791a30ac50ec2bdd2203276bcb9da25ff1115fd6f152548b5110993297d14", size = 36118, upload-time = "2025-10-15T20:48:39.551Z" }, + { url = "https://files.pythonhosted.org/packages/47/83/367902eb1e01955d491251812af2226a7d2b2fa3893c1099b5690119ac44/maxminddb-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:722d4e5c5735f5ae2dcb0ccf972e890c41bccd15476fafe8c2a991f72c4a28d2", size = 36044, upload-time = "2025-10-15T20:48:40.927Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3b/ae5ead6809bc26bd61bab0548d08983a7b2955159df092305eaa45909f6f/maxminddb-3.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c71ec72fdbec8be4d1e9b53294d59f04c7ae73ede6273efce7516995bcb5468", size = 99409, upload-time = "2025-10-15T20:48:42.775Z" }, + { url = "https://files.pythonhosted.org/packages/84/0e/3f4bd90ba3def4b490594866641585b93e3deeef4a09aacde921e223629a/maxminddb-3.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62b55d15f796d14a58c84a853a8ae6d2728546f5c81a15a61aa45082afc21c8", size = 97114, upload-time = "2025-10-15T20:48:44.378Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/49dea88a0931b5764e582e628a55ac2d5a64a8d97ece1853bf751cb43fd3/maxminddb-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78ab0b386ab51ea21d54ca42dee379dfd976d5e650ae0435848278b2aaf9d0fb", size = 96833, upload-time = "2025-10-15T20:48:45.916Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ec/4419c88774ae183bdfb386882796c85675767339eddc41d86ec3df68f61f/maxminddb-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9167c80dac8a524af24af22b2093c819cb2ea11c2abe986b6b29be7fa7f6c88f", size = 95065, upload-time = "2025-10-15T20:48:47.096Z" }, + { url = "https://files.pythonhosted.org/packages/56/40/31fcbe0052848292d8ea17bc3779a8a5f83fd090338a83aeb8c71057ad97/maxminddb-3.0.0-cp311-cp311-win32.whl", hash = "sha256:deb2e6bc068db799eac025ab9d1cbf96cd9fbf636a3414a79518e05fe57ae5a3", size = 35341, upload-time = "2025-10-15T20:48:48.232Z" }, + { url = "https://files.pythonhosted.org/packages/26/53/48e939bc13be41337aa23c30a3b45122e610a8fa8419af44265ff309dfa2/maxminddb-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:87062aec30c57af1a6e1c007391f20f1af836a459486801f169af44bc244c9e7", size = 37132, upload-time = "2025-10-15T20:48:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f0/df860ee2b8db38332416ceab8756a36d5b5ed82d3f1cccc8978cc716548a/maxminddb-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:612497302bf77d7c90586fc3a19b941e0c78b47f92df035e80550f044a849c96", size = 34258, upload-time = "2025-10-15T20:48:50.404Z" }, + { url = "https://files.pythonhosted.org/packages/31/df/dec231686a814f9e279afb39f3e27091770d970964bb94e7bfc1fdf01428/maxminddb-3.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bcf83c60a44ec5dfab9e5d3a0c2347ee429d31fa89f88aa283d8551fd5e2c37a", size = 54352, upload-time = "2025-10-15T20:48:51.488Z" }, + { url = "https://files.pythonhosted.org/packages/14/e3/efb6d621a8940371ecbf393f84fde01f0521116bc281c40124292a593198/maxminddb-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56856d0fadab323fb5dc3fa69bc4cb975242133cab1df2c710779738dadda75d", size = 36328, upload-time = "2025-10-15T20:48:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/51/e8/17cbe454829befb32fec83745141bb6f9ef0b593d53c4e333e938d83ed26/maxminddb-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1bd05d919787719fc1026d53b0e7462cf0c389534620e407676ecf61c2d289bb", size = 36174, upload-time = "2025-10-15T20:48:53.874Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1d/5492205210570d851d5a74f5c9c01022993edc74296eb792c890318eff25/maxminddb-3.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29515dc3606d1d8fffdb4025dccf01c93d16651683e9c6d8611892a4c9f2566d", size = 101153, upload-time = "2025-10-15T20:48:55.013Z" }, + { url = "https://files.pythonhosted.org/packages/a6/07/f96b5e4fdfdd2cc7a9724f3fa40b6bc282c9d9bdcf85b1920a0dee50c00b/maxminddb-3.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52b5edc32894643c93279de2d889c0b98906277e7e91cbba709bc55f5500ecca", size = 99465, upload-time = "2025-10-15T20:48:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/ef2c167277292ce360bcd2a11e0fa9fe2e4e67e7c7b49fff2eab7caae787/maxminddb-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a095ce04e404315f9d47a186a7d96b11a283430d811ba6b0530167233100b95", size = 98395, upload-time = "2025-10-15T20:48:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/04/c9/71ce286a4ba12ec74b094d1a627d57a306349f4f23ce66d3ec2eca045e9f/maxminddb-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d3645a44c392d9ffdea4d2252d70b2910eee47d56b8305da0c0923a63e895d6", size = 97320, upload-time = "2025-10-15T20:48:58.7Z" }, + { url = "https://files.pythonhosted.org/packages/da/4a/3e3f24f876242dd53a8a95250669e2f08b8cd8bc4640e947c982efcdaca6/maxminddb-3.0.0-cp312-cp312-win32.whl", hash = "sha256:c0e6d54da5d85d38e674fee9b04b1ad9212c38cb57adcc7c86bb4ed71b2b6555", size = 35481, upload-time = "2025-10-15T20:49:00.755Z" }, + { url = "https://files.pythonhosted.org/packages/71/de/56feda63d5d8d896c2dcfa6ef9754a429fa2c5353fa5f0c32ed1f46fa004/maxminddb-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:4931ee0cbba030e1b729599e485aca438b668432ccd1eb73770c93bbc38f2b60", size = 37295, upload-time = "2025-10-15T20:49:01.993Z" }, + { url = "https://files.pythonhosted.org/packages/87/4a/87c86516dee431a9e6cdded7eb865b5b7fc7c73b17262a50c75e2da5c9b6/maxminddb-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:f55fb5c607dc4ddab7eba67da92d75921ef7d8e682ab47d21935566dc6990021", size = 34263, upload-time = "2025-10-15T20:49:03.237Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4e/9c97eaea080d450ab63197a20ead71cc652f99f5ecb1e68fc0896db33ac8/maxminddb-3.0.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:f2162e6bee9643d86647518c891756ef5091afb1c2d522fc206d7c26187862eb", size = 37615, upload-time = "2025-10-15T20:49:04.728Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a1/900f105a1562667e22566bbbfa3ca8ca6ea2b2e7e31ff30673459809be74/maxminddb-3.0.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:eace3ccb184546287d27fce54852751e935c00f9ba7b66fece09e7761503cd13", size = 38070, upload-time = "2025-10-15T20:49:06.138Z" }, + { url = "https://files.pythonhosted.org/packages/39/6b/247ef29d080be9b57efba90e33445e2cc028f8cf09beb5e697e5132c95dd/maxminddb-3.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:babf6c600361e5f9bc3e3873b900ab044f6cdc7c0f15c086e0b52d2e005ab949", size = 35365, upload-time = "2025-10-15T20:49:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/7d/dd/ca6c4929e1a634507a99e8143c81ecc3f3f913e1a0c47b656b5a006538ec/maxminddb-3.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:024221e821f3385dd41f13e2d0ac5afca569b69a6d7755c8c960edaf31c0e47f", size = 35919, upload-time = "2025-10-15T20:49:08.3Z" }, + { url = "https://files.pythonhosted.org/packages/e6/41/a7faaf244114d47994fef85accd06dd906832cdcc5465ad27b48e0f11f2d/maxminddb-3.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc838d9060e6e623b4bd055d498a2159a072d43beb3eeaefde5af39ac1b1b249", size = 54354, upload-time = "2025-10-15T20:49:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c8/76b3c0ea1f180209496cb401892a4ad197ee23ac1f370da578fffa466418/maxminddb-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:29fd164067b2765752d5970aaef823a51d262a484c59e866c20dbf99f45453ac", size = 36312, upload-time = "2025-10-15T20:49:10.713Z" }, + { url = "https://files.pythonhosted.org/packages/b6/96/b2d5ab37458ec892d7d52b6a9e6aa9992354d61df20b9978bae60e35d17a/maxminddb-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6008ecba67b7024b80e3f28e276b736f2f984795cd4a6922ddffaba8038d6a60", size = 36174, upload-time = "2025-10-15T20:49:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/ec/3d/c22a117c1c6ca42a62be9473f12d113e2eab72ac28c032a290d0fbbd488e/maxminddb-3.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:684fec138b463d1fc6fa88fd2967e25b3af0629eb0b5e6f3bbc017e64e2f68c6", size = 101205, upload-time = "2025-10-15T20:49:13.342Z" }, + { url = "https://files.pythonhosted.org/packages/df/e6/a170e6ae3492d8e334a6ce9e39668f2b8d0cb0a158804460b5d851315230/maxminddb-3.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2dd94deb1baa19f9fd17968b90b7c03589078a3000972948e3aecfa723300d1", size = 99495, upload-time = "2025-10-15T20:49:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7b/8b/a18aba0838a85bf9ff30f165d3cb5f52967858e89e54aa8a7509a674f253/maxminddb-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:880e233c00a4403bb6dd5e406f156be3c6a5a5b37b472102928014ab21c12b4b", size = 98402, upload-time = "2025-10-15T20:49:15.864Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/85d15613b6dbf2b683a5b9817640c3baac1931edf59a7465c54e0ad92084/maxminddb-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8031353eaece26ee634bcba2cba3bb91092f52a69e5f5dbc5931d59f84b2de", size = 97303, upload-time = "2025-10-15T20:49:17.121Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fb/23883b82abf92d0613375a9a9f4a4412ff8cc0596d124070832bf7f783a6/maxminddb-3.0.0-cp313-cp313-win32.whl", hash = "sha256:2d325fcdbf1ba356ac47304ba3fc8605b21b9bd09d0818b24f43ebecc71c5e29", size = 35481, upload-time = "2025-10-15T20:49:18.266Z" }, + { url = "https://files.pythonhosted.org/packages/4b/19/a498bf14a86e98475d4ca994988e8f072dccfd407d026403ad95725321de/maxminddb-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ad60671645bf88b853f126999cafd0e61ad668f210176ea24a8b5e99dd3e049", size = 37299, upload-time = "2025-10-15T20:49:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/34/ca/36fecfbed3ef0175b569b07f968fb56d591a6effdaeda81f1247dc8034a4/maxminddb-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:4ca8989b0e389404f268c8650aeeadda34417f87baa405325b051a511f56c382", size = 34254, upload-time = "2025-10-15T20:49:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/eed553828b6bcd1bb8c3eb74818aee471c63aba6612128d73c20da122921/maxminddb-3.0.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1a89feae4b7296f24a76467788dad73578bbf51e4cf9672e61ef1be1320dd3d6", size = 37412, upload-time = "2025-10-15T20:49:21.649Z" }, + { url = "https://files.pythonhosted.org/packages/65/3b/0dba6d1d078e2a0523bc0a89c0060b1366a2f9ee8f72f47c33a107e56fab/maxminddb-3.0.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e874238be93b6e6b3c6589795015edb9b935d2638806439ee65c66669e399b2d", size = 37876, upload-time = "2025-10-15T20:49:23.165Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/d4b0636aec3671c3aedf97013b24b6b310de62d6ab373b775a3a8dd594a9/maxminddb-3.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:a45fc20423952f84a73c008d40ea5b1d8c343a3c58d229a45d78a20093817a6f", size = 35334, upload-time = "2025-10-15T20:49:24.554Z" }, + { url = "https://files.pythonhosted.org/packages/37/d7/6ff7c7386365f639cff20fb1d0f4b6533b12706b4a0ae05cfdaf0b41f768/maxminddb-3.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d3796460179976fea3f99855bd75811af74f5659699584d4b7e80a7c66b52893", size = 35887, upload-time = "2025-10-15T20:49:25.614Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ee/e7d8942bbdf06a2082611d52e89ece4e6064195ddb18a7158b5f53e76bc7/maxminddb-3.0.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7997f0b1ed0210b0790a1885e9bc38bed53fdcaaf37141cf8dd1a97894c8fa1b", size = 54335, upload-time = "2025-10-15T20:49:26.728Z" }, + { url = "https://files.pythonhosted.org/packages/77/e1/bd16679264463d2c340940b5b320cc97cd240a3a0b6c1811c88b82d292db/maxminddb-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:21f9d0c164f7c058f419cd9b1105f01606b2abf77b456e8d201700804667686f", size = 36528, upload-time = "2025-10-15T20:49:27.842Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/cf8a5ca1ede1fcd1b306d504a3949d52fd87124fcc2c2180afbcf714ff54/maxminddb-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e7fe7a2a6b2275736fd090d98e081146a1b81abf475d6d2dfd1b106d3792b208", size = 36147, upload-time = "2025-10-15T20:49:28.935Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e0/cd48d467c34ac108fcee9e444dd537e27f04a945d787acd5614f1127dbe5/maxminddb-3.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aaa656d5cb2ea60f4e845669be6a759a25aa1f0cd67fbfef0e64759af28dbb7", size = 101010, upload-time = "2025-10-15T20:49:30.122Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4b/323ec8abe811702bcea537a0aa5e83442f48f1974084bdb048b75424536c/maxminddb-3.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b86dedb1ae681376bcc31bec37d7d674c86ff05687738ab333f18988f17c3a", size = 99249, upload-time = "2025-10-15T20:49:31.332Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/520fcb6ad4185857fbba74cb1ee42b580492049c3730ce0687ac54dbe731/maxminddb-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fde874c2af4dd4488c423b4f4bc2ec9931e5e992f382feb43d07dc4e8dda5e24", size = 98418, upload-time = "2025-10-15T20:49:32.879Z" }, + { url = "https://files.pythonhosted.org/packages/af/20/8650798c8e0806a07e2534c562acf1e3d735ac7aaee0abab370f80c56977/maxminddb-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397a90b6db4563abe6d5fb08e112afd4481b8354fa64d289625c2e941b787860", size = 97092, upload-time = "2025-10-15T20:49:34.529Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/0b89ba3651519c021cf2258a5d0c1af306245f2f5e3e05618f28200270d2/maxminddb-3.0.0-cp314-cp314-win32.whl", hash = "sha256:38ba5ce9efb19f1bf5915f9dca8495f5eb63677cf7760ba7038850d3dcde6572", size = 36144, upload-time = "2025-10-15T20:49:35.702Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9f/06b232bb67f13580ae36aa15ddac85d4b38203363801cdd72d70aaba1b56/maxminddb-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:9c8c62de3b10d1940abd0c18ed57879b6618e26b78a17ab4a576ac30f96a0e83", size = 38069, upload-time = "2025-10-15T20:49:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/70/dd/a76d5b755bdbe24dd1e03f2ce1951d8d9e70fca80abc7498abbec4441f71/maxminddb-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:f0bfd8326a012cb2c8a282531ce900e6535dfc3e50d99c04e64453f782201bd0", size = 34804, upload-time = "2025-10-15T20:49:38.44Z" }, + { url = "https://files.pythonhosted.org/packages/f3/02/512f13edf16e8bf3e01cff958d58bcf023c9ab3ba3d5cda92e011a57f34e/maxminddb-3.0.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6a4330999ab1987f82d32ad969fddcace596dbf8a5c075104e88568f0c326f94", size = 58264, upload-time = "2025-10-15T20:49:39.891Z" }, + { url = "https://files.pythonhosted.org/packages/10/3a/0e8551e0a254489769ab5336bddf5898bead7f6dad17645f85473922a01d/maxminddb-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d22b9706c96872b5ee08a1085af9af986832e52c9cc399aa445f4d3d52f2475", size = 38622, upload-time = "2025-10-15T20:49:41.88Z" }, + { url = "https://files.pythonhosted.org/packages/46/85/1ee105d2870c62df68aa2c6c2b886910de8936d9a67d261e55b0dfc9be53/maxminddb-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:758f1b656c8bf7b5308d23bbcfe61918f1dd57394331e4300402fd2814e748f4", size = 38028, upload-time = "2025-10-15T20:49:43.326Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5f/b01340be810e8a846db21e839a5d80305628765803fabb45aab31d4c96f6/maxminddb-3.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1efe051b84bd90c86571ae7e02479d09dcf6f84925702c2abe870f8cec4a443c", size = 117895, upload-time = "2025-10-15T20:49:45.209Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/06c029169335d3557904749f15e4a03361471869655693c8b83d4b64dd29/maxminddb-3.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a87b1d0409aea9903a5f9f2700abb798c46a115941f28ac23b7905fc3ce3967", size = 114609, upload-time = "2025-10-15T20:49:46.59Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/3dc1c8742e552be3f074943a6fc2e27a6cdaef559613f03b5158833994a4/maxminddb-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:029fce189a447d0c0a5729685ae1af3793de364f7301391cde5604282901c52f", size = 113977, upload-time = "2025-10-15T20:49:47.817Z" }, + { url = "https://files.pythonhosted.org/packages/96/f3/566927372c444dc35f54d17a3a608939586c9b3e5ce9d1282a27ed0e1dde/maxminddb-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0652a3858c6c6f70e94df1eb3e4755087b8278c4280c473fb533280ff2a4d281", size = 111818, upload-time = "2025-10-15T20:49:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/6f97bbb6535cbf0e57cbfff3cde75d3b419d0e192b38d6f33050d6f97ec3/maxminddb-3.0.0-cp314-cp314t-win32.whl", hash = "sha256:b240375d51a91f98d050f3f4f1ed164c0c7e4fb7c55ef7767242ef3d56147853", size = 37395, upload-time = "2025-10-15T20:49:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/41/5296294d494cfc111d000d2e85367a89abe5756f86d2669453c6e1a39334/maxminddb-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b23e77eaa343dccdf85aad422ac16b65ed2181abdc7678945d07e89187a0b15b", size = 39553, upload-time = "2025-10-15T20:49:51.464Z" }, + { url = "https://files.pythonhosted.org/packages/8c/34/7910eff964987e82ffa78b94a59e943e52cd2e51f40a1ffede0e9e6ecf86/maxminddb-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d2306c0a50eeafc882fcfce0c99b44400ae95b9283187902236248c2cf904d2a", size = 35418, upload-time = "2025-10-15T20:49:52.528Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d9/fe6243736aed22a7fa877e4bce7f0a5d45e07115fc0da2b5c261d2d8dc19/maxminddb-3.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:aca2e80a32749e0484e8f0e885014efe0c43218acb6f44c6ca4cd8daa90c8f98", size = 34992, upload-time = "2025-10-15T20:50:00.902Z" }, + { url = "https://files.pythonhosted.org/packages/70/a6/1719d8e980edd735f2ad10e47fe0345a4598cb20658b53b8649e7adecec3/maxminddb-3.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0ea42502167190cc52e11ec9c26ff642ee276418f557304d1773b888563feb3f", size = 34643, upload-time = "2025-10-15T20:50:01.985Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/fef9105639a0abedf2891b2492399417f4e2a166a1bad4f7aa6d262254b9/maxminddb-3.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71db302b3eafab7d421d482fcaa977bf94d696dde4db6aab9a9ffd3f4ea3c186", size = 39163, upload-time = "2025-10-15T20:50:03.074Z" }, + { url = "https://files.pythonhosted.org/packages/e6/88/dd27d7834a5db56e7ac6dca93fd5c4c5f2abd3be3eab0dae2c07023dcb20/maxminddb-3.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98eb4bd49301a9066c6ed5817867b8289af3dc41f60a59e29ee2460d80e348da", size = 37988, upload-time = "2025-10-15T20:50:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/02/c5/fa52785a18300a0a5dfa36d4d640bbaf6dc7f41db102f86293156101bdd0/maxminddb-3.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2de7f8255157107e3ed03690d9dc0c278fd5da108b2f36b8b161e21c67175940", size = 37219, upload-time = "2025-10-15T20:50:05.667Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "oyaml" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/71/c721b9a524f6fe6f73469c90ec44784f0b2b1b23c438da7cc7daac1ede76/oyaml-1.0.tar.gz", hash = "sha256:ed8fc096811f4763e1907dce29c35895d6d5936c4d0400fe843a91133d4744ed", size = 2914, upload-time = "2020-08-18T04:37:43.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/aa/111610d8bf5b1bb7a295a048fc648cec346347a8b0be5881defd2d1b4a52/oyaml-1.0-py2.py3-none-any.whl", hash = "sha256:3a378747b7fb2425533d1ce41962d6921cda075d46bb480a158d45242d156323", size = 2992, upload-time = "2020-08-18T04:37:38.263Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/7b/c0e1333b61d41c69e59e5366e727b18c4992688caf0de1be10b3e5265f6b/pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", size = 22785, upload-time = "2025-10-09T19:12:27.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218, upload-time = "2025-10-09T19:12:24.428Z" }, +] + +[[package]] +name = "pystun3" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/00/a154d38357e2135d96bd633f10159d91f97feebdf54273a9d2e4e92dcc08/pystun3-2.0.0.tar.gz", hash = "sha256:99abfe83df69fd0f6278e9be1456827fafb9e4e25efed8074d15b754ac9b1fee", size = 6717, upload-time = "2024-06-28T14:10:58.821Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/22/dd2f0a266f88d43bfbf619fd3189d22e9248f7bad8d0b14bd34e57c45f54/pystun3-2.0.0-py3-none-any.whl", hash = "sha256:4824c72d2c8c0297df92dbdf7cc57b346d6cf0de4e5a428c3be12d28a8bce0f3", size = 7423, upload-time = "2024-06-28T14:10:57.646Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/16/4b9cfd90d55e66ffdb277d7ebe3bc25250c2311336ec3fc73b2673c794d5/pytokens-0.4.0.tar.gz", hash = "sha256:6b0b03e6ea7c9f9d47c5c61164b69ad30f4f0d70a5d9fe7eac4d19f24f77af2d", size = 15039, upload-time = "2026-01-19T07:59:50.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/05/3196399a353dd4cd99138a88f662810979ee2f1a1cdb0b417cb2f4507836/pytokens-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92eb3ef88f27c22dc9dbab966ace4d61f6826e02ba04dac8e2d65ea31df56c8e", size = 160075, upload-time = "2026-01-19T07:59:00.316Z" }, + { url = "https://files.pythonhosted.org/packages/28/1d/c8fc4ed0a1c4f660391b201cda00b1d5bbcc00e2998e8bcd48b15eefd708/pytokens-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4b77858a680635ee9904306f54b0ee4781effb89e211ba0a773d76539537165", size = 247318, upload-time = "2026-01-19T07:59:01.636Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0e/53e55ba01f3e858d229cd84b02481542f42ba59050483a78bf2447ee1af7/pytokens-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25cacc20c2ad90acb56f3739d87905473c54ca1fa5967ffcd675463fe965865e", size = 259752, upload-time = "2026-01-19T07:59:04.229Z" }, + { url = "https://files.pythonhosted.org/packages/dc/56/2d930d7f899e3f21868ca6e8ec739ac31e8fc532f66e09cbe45d3df0a84f/pytokens-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fab535ebc9079e4db35cd63cb401901c7ce8720a9834f9ad44b9eb4e0f1d4", size = 262842, upload-time = "2026-01-19T07:59:06.14Z" }, + { url = "https://files.pythonhosted.org/packages/42/dd/4e7e6920d23deffaf66e6f40d45f7610dcbc132ca5d90ab4faccef22f624/pytokens-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:4d0f568d7e82b7e96be56d03b5081de40e43c904eb6492bf09aaca47cd55f35b", size = 102620, upload-time = "2026-01-19T07:59:07.839Z" }, + { url = "https://files.pythonhosted.org/packages/3d/65/65460ebbfefd0bc1b160457904370d44f269e6e4582e0a9b6cba7c267b04/pytokens-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd8da894e5a29ba6b6da8be06a4f7589d7220c099b5e363cb0643234b9b38c2a", size = 159864, upload-time = "2026-01-19T07:59:08.908Z" }, + { url = "https://files.pythonhosted.org/packages/25/70/a46669ec55876c392036b4da9808b5c3b1c5870bbca3d4cc923bf68bdbc1/pytokens-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:237ba7cfb677dbd3b01b09860810aceb448871150566b93cd24501d5734a04b1", size = 254448, upload-time = "2026-01-19T07:59:10.594Z" }, + { url = "https://files.pythonhosted.org/packages/62/0b/c486fc61299c2fc3b7f88ee4e115d4c8b6ffd1a7f88dc94b398b5b1bc4b8/pytokens-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01d1a61e36812e4e971cfe2c0e4c1f2d66d8311031dac8bf168af8a249fa04dd", size = 268863, upload-time = "2026-01-19T07:59:12.31Z" }, + { url = "https://files.pythonhosted.org/packages/79/92/b036af846707d25feaff7cafbd5280f1bd6a1034c16bb06a7c910209c1ab/pytokens-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47e2ef3ec6ee86909e520d79f965f9b23389fda47460303cf715d510a6fe544", size = 267181, upload-time = "2026-01-19T07:59:13.856Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c0/6d011fc00fefa74ce34816c84a923d2dd7c46b8dbc6ee52d13419786834c/pytokens-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d36954aba4557fd5a418a03cf595ecbb1cdcce119f91a49b19ef09d691a22ae", size = 102814, upload-time = "2026-01-19T07:59:15.288Z" }, + { url = "https://files.pythonhosted.org/packages/98/63/627b7e71d557383da5a97f473ad50f8d9c2c1f55c7d3c2531a120c796f6e/pytokens-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73eff3bdd8ad08da679867992782568db0529b887bed4c85694f84cdf35eafc6", size = 159744, upload-time = "2026-01-19T07:59:16.88Z" }, + { url = "https://files.pythonhosted.org/packages/28/d7/16f434c37ec3824eba6bcb6e798e5381a8dc83af7a1eda0f95c16fe3ade5/pytokens-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d97cc1f91b1a8e8ebccf31c367f28225699bea26592df27141deade771ed0afb", size = 253207, upload-time = "2026-01-19T07:59:18.069Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/04102856b9527701ae57d74a6393d1aca5bad18a1b1ca48ccffb3c93b392/pytokens-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c8952c537cb73a1a74369501a83b7f9d208c3cf92c41dd88a17814e68d48ce", size = 267452, upload-time = "2026-01-19T07:59:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ef/0936eb472b89ab2d2c2c24bb81c50417e803fa89c731930d9fb01176fe9f/pytokens-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dbf56f3c748aed9310b310d5b8b14e2c96d3ad682ad5a943f381bdbbdddf753", size = 265965, upload-time = "2026-01-19T07:59:20.613Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f5/64f3d6f7df4a9e92ebda35ee85061f6260e16eac82df9396020eebbca775/pytokens-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:e131804513597f2dff2b18f9911d9b6276e21ef3699abeffc1c087c65a3d975e", size = 102813, upload-time = "2026-01-19T07:59:22.012Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/d07e6209f18ef378fc2ae9dee8d1dfe91fd2447c2e2dbfa32867b6dd30cf/pytokens-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0d7374c917197106d3c4761374718bc55ea2e9ac0fb94171588ef5840ee1f016", size = 159968, upload-time = "2026-01-19T07:59:23.07Z" }, + { url = "https://files.pythonhosted.org/packages/0a/73/0eb111400abd382a04f253b269819db9fcc748aa40748441cebdcb6d068f/pytokens-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cd3fa1caf9e47a72ee134a29ca6b5bea84712724bba165d6628baa190c6ea5b", size = 253373, upload-time = "2026-01-19T07:59:24.381Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8d/9e4e2fdb5bcaba679e54afcc304e9f13f488eb4d626e6b613f9553e03dbd/pytokens-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c6986576b7b07fe9791854caa5347923005a80b079d45b63b0be70d50cce5f1", size = 267024, upload-time = "2026-01-19T07:59:25.74Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b7/e0a370321af2deb772cff14ff337e1140d1eac2c29a8876bfee995f486f0/pytokens-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9940f7c2e2f54fb1cb5fe17d0803c54da7a2bf62222704eb4217433664a186a7", size = 270912, upload-time = "2026-01-19T07:59:27.072Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/4348f916c440d4c3e68b53b4ed0e66b292d119e799fa07afa159566dcc86/pytokens-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:54691cf8f299e7efabcc25adb4ce715d3cef1491e1c930eaf555182f898ef66a", size = 103836, upload-time = "2026-01-19T07:59:28.112Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/a693c0cfa9c783a2a8c4500b7b2a8bab420f8ca4f2d496153226bf1c12e3/pytokens-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94ff5db97a0d3cd7248a5b07ba2167bd3edc1db92f76c6db00137bbaf068ddf8", size = 167643, upload-time = "2026-01-19T07:59:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/c0/dd/a64eb1e9f3ec277b69b33ef1b40ffbcc8f0a3bafcde120997efc7bdefebf/pytokens-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0dd6261cd9cc95fae1227b1b6ebee023a5fd4a4b6330b071c73a516f5f59b63", size = 289553, upload-time = "2026-01-19T07:59:30.537Z" }, + { url = "https://files.pythonhosted.org/packages/df/22/06c1079d93dbc3bca5d013e1795f3d8b9ed6c87290acd6913c1c526a6bb2/pytokens-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdca8159df407dbd669145af4171a0d967006e0be25f3b520896bc7068f02c4", size = 302490, upload-time = "2026-01-19T07:59:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/8d/de/a6f5e43115b4fbf4b93aa87d6c83c79932cdb084f9711daae04549e1e4ad/pytokens-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4b5770abeb2a24347380a1164a558f0ebe06e98aedbd54c45f7929527a5fb26e", size = 305652, upload-time = "2026-01-19T07:59:33.685Z" }, + { url = "https://files.pythonhosted.org/packages/ab/3d/c136e057cb622e36e0c3ff7a8aaa19ff9720050c4078235691da885fe6ee/pytokens-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:74500d72c561dad14c037a9e86a657afd63e277dd5a3bb7570932ab7a3b12551", size = 115472, upload-time = "2026-01-19T07:59:34.734Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3c/6941a82f4f130af6e1c68c076b6789069ef10c04559bd4733650f902fd3b/pytokens-0.4.0-py3-none-any.whl", hash = "sha256:0508d11b4de157ee12063901603be87fb0253e8f4cb9305eb168b1202ab92068", size = 13224, upload-time = "2026-01-19T07:59:49.822Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "timeparse-plus" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/9a/3b539d192d478d29e1768e91ccd376950d9c9c8e7e95b4dada94406dba3a/timeparse-plus-1.2.0.tar.gz", hash = "sha256:e559a3485f25e274449706ec3323c9a7b50616779c9c8cbf823c2a6e046d5889", size = 10186, upload-time = "2018-11-14T14:38:51.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/20/e4a0ceb280635d5391cf6c0112b0edb730009f3e4385e85e7349ccceef6a/timeparse_plus-1.2.0-py2.py3-none-any.whl", hash = "sha256:91f4b916184303109175e41d603f6d6a8acacb6a2eb627462936f0ea776cfaf8", size = 9029, upload-time = "2018-11-14T14:38:47.424Z" }, +] + +[[package]] +name = "tox" +version = "4.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/9b/5909f40b281ebd37c2f83de5087b9cb8a9a64c33745f334be0aeaedadbbc/tox-4.34.1.tar.gz", hash = "sha256:ef1e82974c2f5ea02954d590ee0b967fad500c3879b264ea19efb9a554f3cc60", size = 205306, upload-time = "2026-01-09T17:42:59.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/0f/fe6629e277ce615e53d0a0b65dc23c88b15a402bb7dbf771f17bbd18f1c4/tox-4.34.1-py3-none-any.whl", hash = "sha256:5610d69708bab578d618959b023f8d7d5d3386ed14a2392aeebf9c583615af60", size = 176812, upload-time = "2026-01-09T17:42:58.629Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] From 0310b630351aac16b26561342cac5429f6ed2c43 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:12:47 +0100 Subject: [PATCH 02/19] Update README with uv-based development and server setup instructions Add Development setup section with uv sync, tox, and black commands. Simplify Server setup Install dependencies section to use uv. Co-Authored-By: Claude Opus 4.5 --- README.md | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 9e894f6a..9bbad60a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,30 @@ KFO-Server is the official Python-based server for Attorney Online, forked from tsuserver3. +If you are looking to use KFO-Server to set up a server, see [Server setup](#server-setup) below. + +## Development setup + +To set up KFO-Server for development, follow these instructions. + +### Install dependencies + +```bash +uv sync +``` + +### Run tests + +```bash +uv run tox +``` + +### Format code + +```bash +uv run black . +``` + ## Server setup In order to set up the server, you must follow these instructions. This assumes you are familiar with using a terminal. @@ -22,34 +46,13 @@ If you don't want to use Git, you can download the latest zip of KFO-Server [her ### Install dependencies -In order to install dependencies, you will need to open a terminal. - -On Windows, you can do this by pressing Win+R, typing in `cmd`, and pressing Enter. -On Linux, you can do this by pressing Ctrl+Alt+T. - -You should then navigate to the folder where the server is located. - -Take note that depending on your operating system, the command for python may be python3 or python. -You should also verify the version by running `python --version` or `python3 --version`. - -First, we need to create the virtual environment. This can be done by running the following command: +Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then run: ```bash -python -m venv venv +uv sync ``` -Then, we need to activate the virtual environment. -If you're on a unix system (bash or similar), you can run the following command: - -```bash -./venv/bin/pip install -r requirements.txt -``` - -If you're on Windows (cmd), you may have to do this instead: - -```batch -venv\Scripts\pip install -r requirements.txt -``` +This will create a virtual environment and install all dependencies. ### Configure tsuserver From 456dbab4ef7a239176f77de558a014fbb7f30541 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:13:57 +0100 Subject: [PATCH 03/19] update Dockerfile --- Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 62aeb953..69286257 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,14 @@ RUN apk --no-cache add git WORKDIR /app -COPY requirements.txt start_server.py ./ -RUN pip install --upgrade pip -RUN pip install -r requirements.txt +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev --no-install-project COPY server/ server/ COPY migrations/ migrations/ COPY storage/ storage/ +COPY start_server.py ./ -CMD python ./start_server.py +CMD ["uv", "run", "python", "./start_server.py"] From 617a38a035e38ef195cf5280468151a1a366d0e0 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:17:13 +0100 Subject: [PATCH 04/19] Update GitHub workflow to use uv and ruff for linting Replace pip/flake8 with uv/ruff, update action versions, and add ruff to dependencies. Add lint instructions to README. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/pythonapp.yml | 21 +++++++-------------- README.md | 6 ++++++ pyproject.toml | 1 + uv.lock | 28 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 94052e6c..7f54b176 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -11,19 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.12 - uses: actions/setup-python@v1 + - uses: actions/checkout@v4 + - name: Set up uv + uses: astral-sh/setup-uv@v5 with: - python-version: 3.12 + python-version: "3.12" - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + run: uv sync + - name: Lint with ruff + run: uv run ruff check . diff --git a/README.md b/README.md index 9bbad60a..c5c073fb 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ uv run tox uv run black . ``` +### Lint code + +```bash +uv run ruff check . +``` + ## Server setup In order to set up the server, you must follow these instructions. This assumes you are familiar with using a terminal. diff --git a/pyproject.toml b/pyproject.toml index 19acf6ac..87a727d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "arrow>=1.3.0", "black", "discord.py>=2.5.2", + "ruff", "geoip2>=4.7.0", "oyaml>=1.0", "pystun3>=1.0.0", diff --git a/uv.lock b/uv.lock index 1893927d..37e489b1 100644 --- a/uv.lock +++ b/uv.lock @@ -549,6 +549,7 @@ dependencies = [ { name = "pytest" }, { name = "pyyaml" }, { name = "requests" }, + { name = "ruff" }, { name = "timeparse-plus" }, { name = "tox" }, { name = "websockets" }, @@ -566,6 +567,7 @@ requires-dist = [ { name = "pytest" }, { name = "pyyaml", specifier = ">=6.0.1" }, { name = "requests", specifier = ">=2.31.0" }, + { name = "ruff" }, { name = "timeparse-plus", specifier = ">=1.2.0" }, { name = "tox", specifier = ">=4" }, { name = "websockets", specifier = ">=12.0" }, @@ -1077,6 +1079,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "ruff" +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +] + [[package]] name = "six" version = "1.17.0" From dd15a597c1dde3801254f96604b78ea3ec92fe7e Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:18:39 +0100 Subject: [PATCH 05/19] Remove terrible dependency bootstrapping awfulness --- start_server.py | 51 ++----------------------------------------------- 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/start_server.py b/start_server.py index d318d1c4..4876317c 100644 --- a/start_server.py +++ b/start_server.py @@ -1,66 +1,19 @@ -import sys -import subprocess import os - -# Install dependencies in case one is missing -def check_deps(): - py_version = sys.version_info - if py_version.major < 3 or (py_version.major == 3 and py_version.minor < 7): - print( - "tsuserver3 requires at least Python 3.7! Your version: {}.{}".format( - py_version.major, py_version.minor - ) - ) - sys.exit(1) - - try: - import oyaml - import websockets - import arrow - import pytimeparse - import geoip2 - import discord - import stun - except ModuleNotFoundError: - print("Installing dependencies for you...") - try: - subprocess.check_call( - [ - sys.executable, - "-m", - "pip", - "install", - "-r", - "requirements.txt", - ] - ) - print( - "If an import error occurs after the installation, try " - "restarting the server." - ) - except subprocess.CalledProcessError: - print( - "Couldn't install it for you, because you don't have pip, " - "or another error occurred." - ) +from server.tsuserver import TsuServer3 def main(): - from server.tsuserver import TsuServer3 - server = TsuServer3() server.start() if __name__ == "__main__": - print("tsuserver3 - an Attorney Online server") + print("KFO-server - an Attorney Online server") try: - check_deps() main() except KeyboardInterrupt: print("Keyboard interrupt detected, closing server...") except SystemExit: - # Truly idiotproof if os.name == "nt": input("(Press Enter to exit)") From 9e096ffc5d768fda73a41a93f5abb5d2ff287f4f Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:20:16 +0100 Subject: [PATCH 06/19] Fix wrapper scripts --- start-unix.sh | 2 +- start-windows.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/start-unix.sh b/start-unix.sh index c250d3fa..00d5546a 100755 --- a/start-unix.sh +++ b/start-unix.sh @@ -5,7 +5,7 @@ set -euxo pipefail while [ true ] do echo "Starting up server..." - ./venv/bin/python3 start_server.py + uv run python start_server.py echo "Server has shut down. Will restart in 2 seconds (use CTRL-C to cancel)" sleep 2 done diff --git a/start-windows.bat b/start-windows.bat index 2a0e88d9..445e6139 100644 --- a/start-windows.bat +++ b/start-windows.bat @@ -1,7 +1,7 @@ @echo off :while echo Starting up server... -venv\Scripts\python start_server.py +uv run python start_server.py echo Server has shut down, restarting... timeout /t 2 /nobreak goto :while From 95f72f24a232bb01b59350a1f09d1605cf0c05f2 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:26:11 +0100 Subject: [PATCH 07/19] Fix ruff linter errors across codebase - Add ruff configuration to pyproject.toml - Fix wrong decorator @inventory.setter -> @latest_area.setter - Fix undefined target_id variable in area_access.py - Fix missing raise before ArgumentError in character.py - Replace bare except clauses with specific exception types - Auto-fix style issues (membership tests, comparisons, unused imports) Co-Authored-By: Claude Opus 4.5 --- pyproject.toml | 10 +++++++ scripts/music2yaml.py | 1 - server/area.py | 12 ++++----- server/area_manager.py | 10 +++---- server/client_manager.py | 39 ++++++++++++---------------- server/commands/area_access.py | 10 +++---- server/commands/areas.py | 26 +++++++++---------- server/commands/battle.py | 17 ++++++------ server/commands/casing.py | 6 ++--- server/commands/character.py | 24 ++++++++--------- server/commands/fun.py | 2 +- server/commands/hubs.py | 14 +++++----- server/commands/inventory.py | 5 +--- server/commands/messaging.py | 2 +- server/commands/music.py | 10 +++---- server/commands/roleplay.py | 20 +++++++------- server/discordbot.py | 6 ++--- server/evidence.py | 4 +-- server/network/aoprotocol.py | 12 ++++----- server/network/masterserverclient.py | 6 ++--- server/tsuserver.py | 2 +- tests/mock/mocks.py | 1 - 22 files changed, 118 insertions(+), 121 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 87a727d0..d2574023 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,3 +31,13 @@ packages = ["server"] [tool.black] line-length = 120 target-version = ["py311"] + +[tool.ruff] +line-length = 120 +exclude = ["scripts/"] + +[tool.ruff.lint] +ignore = ["E741"] + +[tool.ruff.lint.per-file-ignores] +"server/commands/__init__.py" = ["E402", "F403"] diff --git a/scripts/music2yaml.py b/scripts/music2yaml.py index 1ba69615..69b26798 100644 --- a/scripts/music2yaml.py +++ b/scripts/music2yaml.py @@ -2,7 +2,6 @@ import os import sys -from shutil import copy2 os.chdir(os.path.dirname(__file__)) diff --git a/server/area.py b/server/area.py index 9c1c5e77..de98dde0 100644 --- a/server/area.py +++ b/server/area.py @@ -432,7 +432,7 @@ def load(self, area): self.pos_lock.clear() for pos in _pos_lock: pos = pos.lower() - if pos != "none" and not (pos in self.pos_lock): + if pos != "none" and pos not in self.pos_lock: self.pos_lock.append(pos.lower()) if "evidence_mod" in area: @@ -1236,7 +1236,7 @@ def send_ic(self, if c.area == self and c.listen_pos is not None: if ( type(c.listen_pos) is list - and not (pos in c.listen_pos) + and pos not in c.listen_pos or c.listen_pos == "self" and pos != c.pos ): @@ -2153,8 +2153,8 @@ def vote_end_minigame(self, client): valid_voters = [ c for c in self.clients if not c.hidden and - not c in self.afkers and - not c in self.owners and + c not in self.afkers and + c not in self.owners and c.char_id not in client.area.blue_team and c.char_id not in client.area.red_team ] @@ -2344,9 +2344,9 @@ def play_demo(self, client): if len(self.demo) <= 0: self.stop_demo() return - if not (client in self.owners): + if client not in self.owners: client.send_ooc( - f"[Demo] Playback stopped due to you having insufficient permissions! (Not CM/GM anymore)") + "[Demo] Playback stopped due to you having insufficient permissions! (Not CM/GM anymore)") self.stop_demo() return diff --git a/server/area_manager.py b/server/area_manager.py index 4567352d..994af49a 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -227,7 +227,7 @@ def load(self, hub, destructive=False, ignore=[]): else: self.load_music(f"storage/musiclists/{hub[entry]}.yaml") - if not ("character_data" in ignore) and "character_data" in hub: + if "character_data" not in ignore and "character_data" in hub: try: self.load_character_data(hub["character_data"]) except Exception: @@ -237,7 +237,7 @@ def load(self, hub, destructive=False, ignore=[]): if area == self.default_area(): # Do not remove the default area continue self.remove_area(area) - if not ("areas" in ignore) and "areas" in hub: + if "areas" not in ignore and "areas" in hub: self.load_areas(hub["areas"]) self.broadcast_area_list() @@ -443,7 +443,7 @@ def remove_area(self, area): :param area: target area instance. """ - if not (area in self.areas): + if area not in self.areas: raise AreaError("Area not found.") # Make a copy because it can change size during iteration # (causes runtime error otherwise) @@ -475,9 +475,9 @@ def swap_area(self, area1, area2, fix_links=True): :param area2: second area to swap. """ - if not (area1 in self.areas): + if area1 not in self.areas: raise AreaError("First area not found.") - if not (area2 in self.areas): + if area2 not in self.areas: raise AreaError("Second area not found.") # Grab the indexes a = self.areas.index(area1) diff --git a/server/client_manager.py b/server/client_manager.py index f69710e7..fef2e80c 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -255,19 +255,17 @@ def send_command(self, command, *args): if len(args) > 16 and args[16]: charid_pair = str(args[16]) self_offset_x = 0 - self_offset_y = 0 if len(args) > 19 and args[19]: offset = str(args[19]).replace('', '&').split('&') self_offset_x = offset[0] if len(offset) > 1: - self_offset_y = offset[1] + offset[1] offset_pair_x = 0 - offset_pair_y = 0 if len(args) > 20 and args[20]: offset = str(args[20]).replace('', '&').split('&') offset_pair_x = offset[0] if len(offset) > 1: - offset_pair_y = offset[1] + offset[1] self_offset_x_dro = 500 if self_offset_x: @@ -387,7 +385,7 @@ def send_timer_set_time(self, timer_id=None, new_time=None, start=False): else: self.send_command("TP", timer_id) # pause else: - if new_time == None: + if new_time is None: self.send_command("TI", timer_id, 1, 0) # Stop timer self.send_command("TI", timer_id, 3, 0) # Hide timer else: @@ -450,7 +448,7 @@ def kick_to_latest_area(self): latest_area = self.area.area_manager.get_character_data( self.char_id, "latest_area", None ) - if latest_area == None: + if latest_area is None: return target_area = self.area.area_manager.get_area_by_id(latest_area) @@ -509,7 +507,7 @@ def change_character(self, char_id, force=False): char_id == -1 and not (self.area.area_manager.can_spectate and self.area.can_spectate) and not self.is_mod - and not (self in self.area.owners) + and self not in self.area.owners and not force ): if not self.area.area_manager.can_spectate: @@ -644,13 +642,13 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): if (contains_URL(song)): checked = False # Only if url music is configured to be allowed - if self.server.config["music_allow_url"] == True: + if self.server.config["music_allow_url"]: if len(self.server.music_whitelist) <= 0: checked = True for line in self.server.music_whitelist: if song.startswith(line): checked = True - if checked == False: + if not checked: self.send_ooc( "This URL is not allowed." ) @@ -921,10 +919,7 @@ def construct_music_list(self): self.area.client_music and self.area.area_manager.client_music and self.music_ref != "" - and not ( - self.music_ref - in [self.area.music_ref, self.area.area_manager.music_ref] - ) + and self.music_ref not in [self.area.music_ref, self.area.area_manager.music_ref] and len(self.music_list) > 0 ): if self.replace_music: @@ -1015,7 +1010,7 @@ def set_area(self, area, target_pos=""): self.area.area_manager.send_characters(self) self.char_select() - if len(self.area.pos_lock) > 0 and not (target_pos in self.area.pos_lock): + if len(self.area.pos_lock) > 0 and target_pos not in self.area.pos_lock: target_pos = self.area.pos_lock[0] if self.area.dark: target_pos = self.area.pos_dark @@ -1097,7 +1092,7 @@ def set_area(self, area, target_pos=""): self.send_command("joined_area") # We failed to enter the same area as whoever we've been following, break the follow - if self.following is not None and not (self.following in self.area.clients): + if self.following is not None and self.following not in self.area.clients: self.unfollow() # Record last known area ID for this character if not spectator/gm/mod @@ -1510,7 +1505,7 @@ def get_area_list(self, hidden=False, unlinked=False): if not hidden and area.hidden: continue if len(self.area.links) > 0: - if not (str(area.id) in self.area.links): + if str(area.id) not in self.area.links: if not unlinked: continue if ( @@ -2058,7 +2053,7 @@ def latest_area(self): self.char_id, "latest_area", None ) - @inventory.setter + @latest_area.setter def latest_area(self, value): """Set the character's latest occupied area ID.""" self.area.area_manager.set_character_data( @@ -2181,7 +2176,7 @@ def change_position(self, pos=""): Change the character's current position in the area. :param pos: position in area (Default value = '') """ - if len(self.area.pos_lock) > 0 and not (pos in self.area.pos_lock) and pos != self.area.pos_dark: + if len(self.area.pos_lock) > 0 and pos not in self.area.pos_lock and pos != self.area.pos_dark: poslist = ", ".join(str(l) for l in self.area.pos_lock) raise ClientError(f"Invalid pos! Available pos are {poslist}.") if self.hidden_in is not None: @@ -2214,7 +2209,7 @@ def set_need_call_delay(self): time.time() * 1000.0 + int(self.server.config["need_webhook"]["delay"]) * 1000.0 ) - except: + except Exception: self.need_call_time = round(time.time() * 1000 + 60000) def can_call_case(self): @@ -2258,14 +2253,14 @@ def __init__(self, server): self.delays = {} def set_spam_delay(self, ipid, spam_type, value): - if not str(ipid) in self.delays: + if str(ipid) not in self.delays: self.delays[str(ipid)] = {} self.delays[str(ipid)][spam_type] = value def get_spam_delay(self, ipid, spam_type): - if not str(ipid) in self.delays: + if str(ipid) not in self.delays: return 0 - if not spam_type in self.delays[str(ipid)]: + if spam_type not in self.delays[str(ipid)]: return 0 return self.delays[str(ipid)][spam_type] diff --git a/server/commands/area_access.py b/server/commands/area_access.py index 7b1318ba..8c0c4187 100644 --- a/server/commands/area_access.py +++ b/server/commands/area_access.py @@ -42,7 +42,7 @@ def ooc_cmd_area_lock(client, arg): area_list = client.area.area_manager.get_areas_by_args(args) for area in area_list: if not client.is_mod and client not in area.owners: - if not str(target_id) in client.keys: + if str(area.id) not in client.keys: if area.locking_allowed and area != client.area: client.send_ooc( "You can only lock that area from within!") @@ -151,7 +151,7 @@ def ooc_cmd_area_unlock(client, arg): area_list = client.area.area_manager.get_areas_by_args(args) for area in area_list: if not client.is_mod and client not in area.owners: - if not str(target_id) in client.keys: + if str(area.id) not in client.keys: if area.locking_allowed and area != client.area: client.send_ooc( "You can only unlock that area from within!") @@ -330,7 +330,7 @@ def ooc_cmd_links(client, arg): continue hidden = "📦" - if len(value["evidence"]) > 0 and not (client.hidden_in in value["evidence"]): + if len(value["evidence"]) > 0 and client.hidden_in not in value["evidence"]: # Can't see hidden links if not client.is_mod and client not in client.area.owners: continue @@ -761,7 +761,7 @@ def ooc_cmd_pw(client, arg): link = None password = "" if arg == "": - if not client.is_mod and not (client in client.area.owners): + if not client.is_mod and client not in client.area.owners: raise ArgumentError( "You are not allowed to see this area's password. Use /pw [password]" ) @@ -827,7 +827,7 @@ def ooc_cmd_setpw(client, arg): area = client.area.area_manager.get_area_by_id(int(args[0])) if len(args) > 1: password = args[1] - if not client.is_mod and not (client in area.owners): + if not client.is_mod and client not in area.owners: raise ClientError("You do not own that area!") if link is not None: link["password"] = password diff --git a/server/commands/areas.py b/server/commands/areas.py index 7d8c5805..6741545a 100644 --- a/server/commands/areas.py +++ b/server/commands/areas.py @@ -56,11 +56,11 @@ def ooc_cmd_overlay(client, arg): raise AreaError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") - if client.area.dark and not client.is_mod and not (client in client.area.owners): + if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") try: client.area.change_background(client.area._background, overlay=arg) @@ -83,11 +83,11 @@ def ooc_cmd_overlay_clear(client, arg): raise AreaError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") - if client.area.dark and not client.is_mod and not (client in client.area.owners): + if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") try: client.area.change_background(client.area._background, overlay="") @@ -119,11 +119,11 @@ def ooc_cmd_bg(client, arg): raise AreaError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") - if client.area.dark and not client.is_mod and not (client in client.area.owners): + if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") try: client.area.change_background(arg) @@ -567,7 +567,7 @@ def ooc_cmd_pos_lock(client, arg): pos = client.area.pos_dark client.send_ooc(f"Current darkness pos is {pos}.") return - if not client.is_mod and not (client in client.area.owners): + if not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") client.area.pos_dark = arg client.area.broadcast_ooc(f"Locked darkness pos into {arg}.") @@ -584,7 +584,7 @@ def ooc_cmd_pos_lock(client, arg): ooc_cmd_pos_lock_clear(client, arg) return - if not client.is_mod and not (client in client.area.owners): + if not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") client.area.pos_lock.clear() @@ -644,7 +644,7 @@ def ooc_cmd_knock(client, arg): allowed = client.is_mod or client in area.owners or client in client.area.owners if not allowed and area != client.area: if len(client.area.links) > 0: - if not str(area.id) in client.area.links: + if str(area.id) not in client.area.links: raise ClientError( f"Failed to knock on [{area.id}] {area.name}: That area is inaccessible!" ) @@ -819,7 +819,7 @@ def ooc_cmd_desc(client, arg): raise ClientError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") @@ -828,7 +828,7 @@ def ooc_cmd_desc(client, arg): ooc_cmd_desc_clear(client, "") return if client.area.dark: - if not client.is_mod and not (client in client.area.owners): + if not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") client.area.desc_dark = arg.strip() else: @@ -854,12 +854,12 @@ def ooc_cmd_desc_clear(client, arg): raise ClientError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") if client.area.dark: - if not client.is_mod and not (client in client.area.owners): + if not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") client.area.desc_dark = "" else: diff --git a/server/commands/battle.py b/server/commands/battle.py index 20231eb8..cdfb5b8f 100644 --- a/server/commands/battle.py +++ b/server/commands/battle.py @@ -7,7 +7,6 @@ from server.constants import derelative from . import mod_only -from .. import commands __all__ = [ "ooc_cmd_choose_fighter", @@ -77,7 +76,7 @@ def send_info_fighter(client): Prepare the message about fighter info """ msg = f"\n👤 {client.battle.fighter} 👤:\n" - if client.battle.status != None: + if client.battle.status is not None: msg += f"Status 🌈: {client.battle.status}\n" msg += f"\nHP 💗: {round(client.battle.hp,2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana,2)}\nATK 🗡️: {round(client.battle.atk,2)}\nDEF 🛡️: {round(client.battle.defe,2)}\nSPA ✨: {round(client.battle.spa,2)}\nSPD 🔮: {round(client.battle.spd,2)}\nSPE 💨: {round(client.battle.spe,2)}\n\n" for move in client.battle.moves: @@ -96,7 +95,7 @@ def send_stats_fighter(client): Prepare the message about fighter stats """ msg = f"\n👤 {client.battle.fighter} 👤:\n" - if client.battle.status != None: + if client.battle.status is not None: msg += f"Status 🌈: {client.battle.status}\n" msg += f"\nHP 💗: {round(client.battle.hp,2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana,2)}\nATK 🗡️: {round(client.battle.atk,2)}\nDEF 🛡️: {round(client.battle.defe,2)}\nSPA ✨: {round(client.battle.spa,2)}\nSPD 🔮: {round(client.battle.spd,2)}\nSPE 💨: {round(client.battle.spe,2)}\n\n" client.send_ooc(msg) @@ -524,7 +523,7 @@ def ooc_cmd_fight(client, arg): client.area.broadcast_ooc( f"⚔️{client.battle.fighter} ({client.showname}) is ready to fight!⚔️" ) - fighter_name = client.area.area_manager.char_list[client.char_id] + client.area.area_manager.char_list[client.char_id] battle_send_ic(client, msg=f"~{client.battle.fighter}~ is ready to fight") @@ -664,7 +663,7 @@ def ooc_cmd_skip_move(client, arg): client.battle.selected_move = -2 client.area.num_selected_move += 1 - client.send_ooc(f"You have choosen to skip the turn") + client.send_ooc("You have choosen to skip the turn") client.area.broadcast_ooc(f"{client.battle.fighter} has choosen a move") if client.area.num_selected_move == len(client.area.fighters): @@ -1128,7 +1127,7 @@ def start_battle_animation(area): if len(targets) == 1: battle_send_ic( client, - msg=f"and tries to help but the target is already down", + msg="and tries to help but the target is already down", ) continue if "heal" in move.effect: @@ -1259,7 +1258,7 @@ def start_battle_animation(area): if client.battle.status == "enraged": client.battle.status = None damage = damage * area.battle_enraged_bonus - battle_send_ic(client, msg=f"focuses all strenght") + battle_send_ic(client, msg="focuses all strenght") # send ic damage move if damage == 0: @@ -1491,7 +1490,7 @@ def start_battle_animation(area): for client in area.fighters: if client.battle.hp <= 0: continue - fighter_name = client.area.area_manager.char_list[client.char_id] + client.area.area_manager.char_list[client.char_id] if client.battle.status == "poison" and client.battle.hp > 0: client.battle.hp += -client.battle.maxhp / area.battle_poison_damage battle_send_ic( @@ -1575,7 +1574,7 @@ def start_battle_animation(area): winner.battle.guild = guild area.fighters = [] elif len(area.fighters) == 0: - battle_send_ic(client, msg=f"~Everyone~ is down...", offset=100) + battle_send_ic(client, msg="~Everyone~ is down...", offset=100) area.fighters = [] else: # check if there is a winner guild diff --git a/server/commands/casing.py b/server/commands/casing.py index d0001a4e..10d61f07 100644 --- a/server/commands/casing.py +++ b/server/commands/casing.py @@ -64,7 +64,7 @@ def ooc_cmd_doc(client, arg): raise ClientError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") @@ -86,7 +86,7 @@ def ooc_cmd_cleardoc(client, arg): raise ClientError("You are not on the area's invite list!") if ( not client.is_mod - and not (client in client.area.owners) + and client not in client.area.owners and client.char_id == -1 ): raise ClientError("You may not do that while spectating!") @@ -1107,7 +1107,7 @@ def ooc_cmd_evidence_save(client, arg): ) with open(arg, "w", encoding="utf-8") as yaml_save: yaml.dump(evidence, yaml_save) - database.log_area(f"evidence.save", client, client.area, arg) + database.log_area("evidence.save", client, client.area, arg) client.send_ooc( f"Evidence has been saved as '{arg}' on the server." ) diff --git a/server/commands/character.py b/server/commands/character.py index 36fd2fc1..e141b40b 100644 --- a/server/commands/character.py +++ b/server/commands/character.py @@ -314,7 +314,7 @@ def force_switch(client, target, char=""): except ClientError: raise else: - target.send_ooc(f"You've been forced into character select screen.") + target.send_ooc("You've been forced into character select screen.") target.char_select() @@ -341,7 +341,7 @@ def ooc_cmd_kill(client, arg): try: for target in targets: force_switch(client, target, "-1") - target.send_ooc(f"💀You are dead!💀") + target.send_ooc("💀You are dead!💀") except Exception as ex: raise ArgumentError( f"Error encountered: {ex}. Use /kill as a mod or area owner." @@ -403,8 +403,8 @@ def ooc_cmd_charcurse(client, arg): str(client.area.area_manager.char_list[cid]) + "," log_msg += " " + \ str(client.area.area_manager.char_list[cid]) + "," - except: - ArgumentError( + except Exception: + raise ArgumentError( "" + str(raw_cid) + " does not look like a valid character ID." ) @@ -943,7 +943,7 @@ def mod_keys(client, arg, mod=0): target, "keys", []) if a in keys and mod == 2: keys.remove(a) - elif not (a in keys): + elif a not in keys: keys.append(a) client.area.area_manager.set_character_data(target, "keys", keys) client.send_ooc( @@ -1004,7 +1004,7 @@ def ooc_cmd_keys(client, arg): if len(args) < 1: client.send_ooc(f"Your current keys are {client.keys}") return - if not client.is_mod and not (client in client.area.area_manager.owners): + if not client.is_mod and client not in client.area.area_manager.owners: raise ClientError("Only mods and GMs can check other people's keys.") if len(args) == 1: try: @@ -1112,7 +1112,7 @@ def ooc_cmd_chardesc_clear(client, arg): ) client.area.broadcast_player_list() else: - client.send_ooc(f"You cleared your character description.") + client.send_ooc("You cleared your character description.") database.log_area( "chardesc.clear", client, client.area ) @@ -1403,12 +1403,12 @@ def get_latest_area(client, char_id: int): char_folder = None if char_id in range(0, len(client.area.area_manager.char_list)): char_folder = client.area.area_manager.char_list[char_id] - if char_folder == None: + if char_folder is None: print(char_folder, ' ', char_id) - client.send_ooc(f"Can't get latest area when spectating!") + client.send_ooc("Can't get latest area when spectating!") return None latest_area_id = client.area.area_manager.get_character_data(char_id, "latest_area", None) - if latest_area_id == None: + if latest_area_id is None: client.send_ooc(f"{char_folder} has no latest occupied area defined!") return None target_area = None @@ -1444,7 +1444,7 @@ def ooc_cmd_get_latest_area(client, arg): if area: client.send_ooc(f"{client.area.area_manager.char_list[target_charid]} latest occupied area is [{area.id}] {area.name}.") else: - client.send_ooc(f"Area not found!") + client.send_ooc("Area not found!") @mod_only(hub_owners=True) def ooc_cmd_kick_to_latest_area(client, arg): @@ -1521,7 +1521,7 @@ def ooc_cmd_set_latest_area(client, arg): if target_charid in range(0, len(client.area.area_manager.char_list)): char_folder = client.area.area_manager.char_list[target_charid] if not char_folder: - client.send_ooc(f"Invalid character id!") + client.send_ooc("Invalid character id!") return None if len(args) >= 2: to_area = int(args[1]) diff --git a/server/commands/fun.py b/server/commands/fun.py index e5ae955c..94f3218f 100644 --- a/server/commands/fun.py +++ b/server/commands/fun.py @@ -1,6 +1,6 @@ from server import database from server.constants import TargetType -from server.exceptions import ClientError, ArgumentError +from server.exceptions import ArgumentError from . import mod_only diff --git a/server/commands/hubs.py b/server/commands/hubs.py index 64b620e2..7e317681 100644 --- a/server/commands/hubs.py +++ b/server/commands/hubs.py @@ -132,7 +132,7 @@ def ooc_cmd_save_hub(client, arg): if os.path.isfile(f"storage/hubs/{name}.yaml") and len(args) > 2 and args[1].lower() == "read_only": try: os.remove(f"storage/hubs/{name}.yaml") - except: + except OSError: raise AreaError(f"{name} hasn't been removed from write and read folder!") name = f"{path}/{name}.yaml" hub = client.area.area_manager.save(ignore=["can_gm", "max_areas"]) @@ -253,14 +253,14 @@ def ooc_cmd_list_hubs(client, arg): try: if F.lower().endswith(".yaml"): hubs_read_only.append(F[:-5]) - except: + except Exception: continue for F in os.listdir("storage/hubs/"): try: if F.lower().endswith(".yaml"): hubs_editable.append(F[:-5]) - except: + except Exception: continue hubs_read_only.sort() @@ -507,9 +507,9 @@ def ooc_cmd_area_pref(client, arg): msg = "Current preferences:" for attri in client.area.__dict__.keys(): value = getattr(client.area, attri) - if not (type(value) is bool): + if type(value) is not bool: continue - mod = "[gm] " if not (attri in cm_allowed) else "" + mod = "[gm] " if attri not in cm_allowed else "" msg += f"\n* {mod}{attri}={value}" client.send_ooc(msg) return @@ -523,12 +523,12 @@ def ooc_cmd_area_pref(client, arg): try: cmd = args[0].lower() attri = getattr(client.area, cmd) - if not (type(attri) is bool): + if type(attri) is not bool: raise ArgumentError("Preference is not a boolean.") if ( not client.is_mod and client not in client.area.area_manager.owners - and not (cmd in cm_allowed) + and cmd not in cm_allowed ): raise ClientError("You need to be a GM to modify this preference.") tog = not attri diff --git a/server/commands/inventory.py b/server/commands/inventory.py index ccf99b62..91c8f0e4 100644 --- a/server/commands/inventory.py +++ b/server/commands/inventory.py @@ -1,11 +1,8 @@ import shlex -import yaml -import os -import re from server import database -from server.constants import TargetType, derelative +from server.constants import TargetType from server.exceptions import ClientError, ServerError, ArgumentError, AreaError from . import mod_only diff --git a/server/commands/messaging.py b/server/commands/messaging.py index 1990c542..c5581a7f 100644 --- a/server/commands/messaging.py +++ b/server/commands/messaging.py @@ -1,6 +1,6 @@ from server import database from server.constants import TargetType -from server.exceptions import ClientError, ArgumentError, AreaError +from server.exceptions import ClientError, ArgumentError from . import mod_only diff --git a/server/commands/music.py b/server/commands/music.py index 546ae92d..74f7641f 100644 --- a/server/commands/music.py +++ b/server/commands/music.py @@ -4,8 +4,8 @@ import yaml from server import database -from server.constants import TargetType, derelative, contains_URL -from server.exceptions import ClientError, ServerError, ArgumentError, AreaError +from server.constants import TargetType, derelative +from server.exceptions import ClientError, ArgumentError, AreaError from . import mod_only @@ -259,14 +259,14 @@ def ooc_cmd_musiclists(client, arg): try: if F.lower().endswith(".yaml"): musiclist_read_only.append(F[:-5]) - except: + except Exception: continue for F in os.listdir("storage/musiclists/"): try: if F.lower().endswith(".yaml"): musiclist_editable.append(F[:-5]) - except: + except Exception: continue musiclist_read_only.sort() @@ -443,7 +443,7 @@ def ooc_cmd_musiclist_save(client, arg): if os.path.isfile(f"storage/musiclists/{name}.yaml") and len(args) > 2 and args[1].lower() == "read_only": try: os.remove(f"storage/musiclists/{name}.yaml") - except: + except OSError: raise AreaError(f"{args[0]} hasn't been removed from write and read folder!") with open(filepath, "w", encoding="utf-8") as yaml_save: diff --git a/server/commands/roleplay.py b/server/commands/roleplay.py index 84cff861..da8bd585 100644 --- a/server/commands/roleplay.py +++ b/server/commands/roleplay.py @@ -285,7 +285,7 @@ def ooc_cmd_notecard_reveal(client, arg): else: client.area.cards.clear() client.area.broadcast_ooc( - f"Notecards have been cleared." + "Notecards have been cleared." ) database.log_area("notecard_reveal", client, client.area) @@ -433,7 +433,7 @@ def ooc_cmd_vote_reveal(client, arg): else: client.area.votes.clear() client.area.broadcast_ooc( - f"Votes have been cleared." + "Votes have been cleared." ) database.log_area("vote_reveal", client, client.area) @@ -670,7 +670,7 @@ def ooc_cmd_rps(client, arg): if winner: msg += f"\n  🏆[{winner.id}] {winner.showname} WINS!!!🏆" else: - msg += f"\n  👔It's a tie!👔" + msg += "\n  👔It's a tie!👔" # Announce the message! client.area.broadcast_ooc(msg) @@ -796,12 +796,12 @@ def ooc_cmd_timer(client, arg): client.send_ooc(f"Timer {timer_id} is unset.") return - if not (client in client.area.owners) and not client.is_mod: + if client not in client.area.owners and not client.is_mod: raise ArgumentError( "Only CMs or GMs can modify timers. Usage: /timer ") if ( timer_id == 0 - and not (client in client.area.area_manager.owners) + and client not in client.area.area_manager.owners and not client.is_mod ): raise ArgumentError( @@ -885,7 +885,7 @@ def ooc_cmd_timer(client, arg): # Send static time if applicable if timer.set: - s = int(not timer.started) + int(not timer.started) static_time = int(timer.static.total_seconds()) * 1000 if timer_id == 0: client.area.area_manager.send_timer_set_time(timer_id, static_time, timer.started) @@ -1027,7 +1027,7 @@ def ooc_cmd_format_timer(client, arg): args = shlex.split(arg) try: args[0] = int(args[0]) - except: + except (ValueError, IndexError): return if args[0] == 0: if client.is_mod or client in client.area.area_manager.owners: @@ -1072,7 +1072,7 @@ def ooc_cmd_timer_interval(client, arg): args = shlex.split(arg) try: args[0] = int(args[0]) - except: + except (ValueError, IndexError): raise ArgumentError("Timer ID should be an integer") if args[0] == 0: if client.is_mod or client in client.area.area_manager.owners: @@ -1092,10 +1092,10 @@ def ooc_cmd_timer_interval(client, arg): return try: if len(args) == 1: - timer.interval = 16 + timer.interval = 16 else: timer.interval = pytimeparse.parse(args[1]) * 1000 - except: + except (TypeError, ValueError): raise ArgumentError("Interval value not valid!") if timer.set: client.send_timer_set_interval(args[0], timer) diff --git a/server/discordbot.py b/server/discordbot.py index 0caefe5c..634c9b6c 100644 --- a/server/discordbot.py +++ b/server/discordbot.py @@ -70,7 +70,7 @@ async def announcing(ctx, name=None, description=None, url=None, additional=None async def gethubs(interaction: discord.Interaction): msg = "" number_players = int(self.server.player_count) - msg += f"**Clients in Areas**\n" + msg += "**Clients in Areas**\n" for hub in self.server.hub_manager.hubs: if len(hub.clients) == 0: continue @@ -84,9 +84,9 @@ async def gethubs(interaction: discord.Interaction): continue msg += f"\t**[{area.id}] {area.name} (users: {len(area.clients)}) [{area.status}]" if area.locked: - msg += f" [LOCKED]" + msg += " [LOCKED]" elif area.muted: - msg += f" [SPECTATABLE]" + msg += " [SPECTATABLE]" if area.get_owners() != "": msg += f" [CM(s): {area.get_owners()}]" msg += "**\n" diff --git a/server/evidence.py b/server/evidence.py index 1d98ea06..aefccb58 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -1,8 +1,6 @@ -import re from server import commands from server.exceptions import ClientError, AreaError, ArgumentError, ServerError -from server.constants import encode_ao_packet class EvidenceList: """Contains a list of evidence items.""" @@ -22,7 +20,7 @@ def __init__(self, name, desc, image, pos, can_hide_in=False, show_in_dark=0, tr self.show_in_dark = show_in_dark self.hiding_client = None self.triggers = triggers - if triggers == None: + if triggers is None: self.triggers = {} def set_name(self, name): diff --git a/server/network/aoprotocol.py b/server/network/aoprotocol.py index 9af8f8d2..30508988 100644 --- a/server/network/aoprotocol.py +++ b/server/network/aoprotocol.py @@ -729,7 +729,7 @@ def net_cmd_ms(self, args): len(showname) > 0 and not self.client.area.showname_changes_allowed and not self.client.is_mod - and not (self.client in self.client.area.owners) + and self.client not in self.client.area.owners ): self.client.send_ooc( "Showname changes are forbidden in this area!") @@ -755,7 +755,7 @@ def net_cmd_ms(self, args): frames_sfx = derelative(frames_sfx) effect = derelative(effect) - if not self.client.is_mod and not (self.client in self.client.area.owners): + if not self.client.is_mod and self.client not in self.client.area.owners: if not self.client.area.blankposting_allowed: # Regex is slow as hell, need to change this to be more performant if text.strip() == "" or ( @@ -919,7 +919,7 @@ def net_cmd_ms(self, args): if (nonint_pre == 1 and button in range(1, 4)) or ( self.client.area.non_int_pres_only and not self.client.is_mod - and not (self.client in self.client.area.owners) + and self.client not in self.client.area.owners ): if emote_mod == 1 or emote_mod == 2: emote_mod = 0 @@ -930,7 +930,7 @@ def net_cmd_ms(self, args): if ( not self.client.area.shouts_allowed and not self.client.is_mod - and not (self.client in self.client.area.owners) + and self.client not in self.client.area.owners ): # Old clients communicate the objecting in emote_mod. if emote_mod == 2: @@ -955,7 +955,7 @@ def net_cmd_ms(self, args): if ( self.server.config["block_repeat"] and not self.client.is_mod - and not (self.client in self.client.area.owners) + and self.client not in self.client.area.owners and text.strip() != "" and self.client.area.last_ic_message is not None and cid == self.client.area.last_ic_message[8] @@ -1297,7 +1297,7 @@ def net_cmd_ms(self, args): video, # 34 ) a_list = ", ".join([str(a.id) for a in target_area]) - if not (self.client.area in target_area): + if self.client.area not in target_area: if msg == "": msg = " " self.client.send_command( diff --git a/server/network/masterserverclient.py b/server/network/masterserverclient.py index af61caef..1ce0ac09 100644 --- a/server/network/masterserverclient.py +++ b/server/network/masterserverclient.py @@ -49,7 +49,7 @@ async def connect(self): except aiohttp.ClientError: # Masterserver is down or unreachable, may be temporary so log it as a warning logger.warning('Failed to connect to the master server') - except Exception as err: + except Exception: # Unknown error occurred, log it as a hard error with full exception information exc_type, exc_value, exc_traceback = sys.exc_info() logger.error("Uncaught exception while advertising server to masterserver") @@ -116,7 +116,7 @@ async def send_server_info(self, http: aiohttp.ClientSession): def add_ws_info(self, advertise_body: dict) -> None: cfg = self.server.config - if 'use_websockets' not in cfg or cfg['use_websockets'] == False: + if 'use_websockets' not in cfg or not cfg['use_websockets']: # Explicitly disabled, return return @@ -132,6 +132,6 @@ def add_ws_info(self, advertise_body: dict) -> None: advertise_body['ws_port'] = ws_port - if 'use_securewebsockets' in cfg and cfg['use_securewebsockets'] == True: + if 'use_securewebsockets' in cfg and cfg['use_securewebsockets']: if 'secure_websocket_port' in cfg and cfg['secure_websocket_port']: advertise_body['wss_port'] = cfg['secure_websocket_port'] diff --git a/server/tsuserver.py b/server/tsuserver.py index 0db0d46f..1abcfe19 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -521,7 +521,7 @@ def send_arup(self, client, args): def send_discord_chat(self, name, message, hub_id=0, area_id=0): area = self.hub_manager.get_hub_by_id(hub_id).get_area_by_id(area_id) - cid = area.area_manager.get_char_id_by_name( + area.area_manager.get_char_id_by_name( self.config["bridgebot"]["character"]) message = dezalgo(message) message = remove_URL(message) diff --git a/tests/mock/mocks.py b/tests/mock/mocks.py index 85120568..0f5b7fb5 100644 --- a/tests/mock/mocks.py +++ b/tests/mock/mocks.py @@ -1,4 +1,3 @@ -import asyncio from typing import Callable, Optional From b97bf656a72e5ec23ddea7fa1959fd440f37be52 Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:27:40 +0100 Subject: [PATCH 08/19] Add comments explaining ruff ignore rules Co-Authored-By: Claude Opus 4.5 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d2574023..2b9d579e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,10 +34,14 @@ target-version = ["py311"] [tool.ruff] line-length = 120 +# Exclude utility scripts from linting exclude = ["scripts/"] [tool.ruff.lint] +# E741: Ambiguous variable name (e.g., 'l') - used intentionally in list comprehensions ignore = ["E741"] [tool.ruff.lint.per-file-ignores] +# E402: Module level import not at top of file - imports must come after __all__ definitions +# F403: Star imports - intentional for re-exporting command modules "server/commands/__init__.py" = ["E402", "F403"] From fd1c39e8358dedd12e9b4166ebc9a72bb24e5a2b Mon Sep 17 00:00:00 2001 From: David Skoland Date: Fri, 23 Jan 2026 12:30:58 +0100 Subject: [PATCH 09/19] Ran Black formatter --- scripts/music2yaml.py | 21 +- server/area.py | 463 +++++++++------------- server/area_manager.py | 49 +-- server/client_manager.py | 548 +++++++++------------------ server/commands/__init__.py | 12 +- server/commands/admin.py | 77 ++-- server/commands/area_access.py | 216 +++-------- server/commands/areas.py | 304 ++++----------- server/commands/battle.py | 248 +++++------- server/commands/casing.py | 218 +++-------- server/commands/character.py | 391 ++++++------------- server/commands/fun.py | 23 +- server/commands/hubs.py | 259 ++++--------- server/commands/inventory.py | 58 ++- server/commands/messaging.py | 23 +- server/commands/music.py | 107 ++---- server/commands/roleplay.py | 275 +++++--------- server/constants.py | 25 +- server/database.py | 183 +++------ server/discordbot.py | 26 +- server/emotes.py | 33 +- server/evidence.py | 36 +- server/hub_manager.py | 18 +- server/logger.py | 7 +- server/network/aoprotocol_ws.py | 5 +- server/network/masterserverclient.py | 48 ++- server/network/webhooks.py | 60 +-- server/tsuserver.py | 71 ++-- 28 files changed, 1232 insertions(+), 2572 deletions(-) diff --git a/scripts/music2yaml.py b/scripts/music2yaml.py index 69b26798..34f42f69 100644 --- a/scripts/music2yaml.py +++ b/scripts/music2yaml.py @@ -2,7 +2,6 @@ import os import sys - os.chdir(os.path.dirname(__file__)) arg = "" @@ -23,9 +22,7 @@ " use_unique_folder: True #If true, this music will be contained entirely within its own folder e.g. base/music//*.mp3\n" ) if droppedFile: - input( - "Writing .ogg and .mp3 and categorizing them based on file argument to music.yaml. Press ENTER to begin." - ) + input("Writing .ogg and .mp3 and categorizing them based on file argument to music.yaml. Press ENTER to begin.") for line in droppedFile.readlines(): try: line = line.rstrip() @@ -41,18 +38,12 @@ # tag = TinyTag.get(line) # duration = tag.duration duration = -1 - File.write( - ' - name: "{}"\n'.format(current_category + "/" + line) - ) + File.write(' - name: "{}"\n'.format(current_category + "/" + line)) File.write(" length: {}\n".format(duration)) print("Name: {} Length: {}".format(line, duration)) except: Err.write("Error for {}\n".format(line)) - input( - "Unable to process song: {}! Press ENTER to continue.".format( - line - ) - ) + input("Unable to process song: {}! Press ENTER to continue.".format(line)) except: continue else: @@ -87,11 +78,7 @@ # tag = TinyTag.get(line) # duration = tag.duration duration = -1 - File.write( - ' - name: "{}"\n'.format( - current_category + "/" + song.name - ) - ) + File.write(' - name: "{}"\n'.format(current_category + "/" + song.name)) File.write(" length: {}\n".format(duration)) print("Name: {} Length: {}".format(song.name, duration)) diff --git a/server/area.py b/server/area.py index de98dde0..ca4e198d 100644 --- a/server/area.py +++ b/server/area.py @@ -206,7 +206,7 @@ def __init__(self, area_manager, name): self.red_team = set() self.blue_team = set() # Clients who cast votes - self.votes_cast = set() + self.votes_cast = set() # What percentage of valid voters needs to vote to force-end the minigame, rounded self.votes_percentage = 0.7 # Minigame name @@ -294,7 +294,7 @@ def __init__(self, area_manager, name): self.auto_pair = False self.auto_pair_max = "triple" self.auto_pair_cycle = False - + # list of areas to broadcast ic messages to self.broadcast_list = [] @@ -307,8 +307,7 @@ def name(self): def name(self, value): self._name = value.strip() while "" in self._name or "" in self._name: - self._name = self._name.replace( - "", "").replace("", "") + self._name = self._name.replace("", "").replace("", "") self.abbreviation = self.abbreviate() @property @@ -557,12 +556,12 @@ def load(self, area): self.pos_dark = area["pos_dark"] if "desc_dark" in area: self.desc_dark = area["desc_dark"] - if 'passing_msg' in area: - self.passing_msg = area['passing_msg'] - if 'msg_delay' in area: - self.msg_delay = area['msg_delay'] - if 'present_reveals_evidence' in area: - self.present_reveals_evidence = area['present_reveals_evidence'] + if "passing_msg" in area: + self.passing_msg = area["passing_msg"] + if "msg_delay" in area: + self.msg_delay = area["msg_delay"] + if "present_reveals_evidence" in area: + self.present_reveals_evidence = area["present_reveals_evidence"] if "evidence" in area and len(area["evidence"]) > 0: self.evi_list.evidences.clear() @@ -592,8 +591,7 @@ def load(self, area): evidence = value["evidence"] if "password" in value: password = value["password"] - self.link(key, locked, hidden, target_pos, - can_peek, evidence, password) + self.link(key, locked, hidden, target_pos, can_peek, evidence, password) # Update the clients in that area if self.dark: @@ -607,9 +605,7 @@ def load(self, area): if self.music_autoplay: for client in self.clients: if self.music != client.playing_audio[0]: - client.send_command( - "MC", self.music, -1, "", self.music_looping, 0, self.music_effects - ) + client.send_command("MC", self.music, -1, "", self.music_looping, 0, self.music_effects) if "can_battle" in area: self.can_battle = area["can_battle"] @@ -709,7 +705,7 @@ def play_client_ambience(self, client): client.send_command( "area_ambient", # for compatibility with the KFO method, navigate out of sounds/ambience into sounds/music - "../music/"+self.ambience, + "../music/" + self.ambience, ) else: # AO packet @@ -722,8 +718,7 @@ def play_client_ambience(self, client): "", 1, 1, - int(MusicEffect.FADE_OUT | - MusicEffect.FADE_IN | MusicEffect.SYNC_POS), + int(MusicEffect.FADE_OUT | MusicEffect.FADE_IN | MusicEffect.SYNC_POS), ) def new_client(self, client): @@ -733,9 +728,7 @@ def new_client(self, client): database.log_area("area.join", client, self) if self.music_autoplay and self.music != client.playing_audio[0]: - client.send_command( - "MC", self.music, -1, "", self.music_looping, 0, self.music_effects - ) + client.send_command("MC", self.music, -1, "", self.music_looping, 0, self.music_effects) # Update the timers for the client self.update_timers(client) @@ -802,8 +795,7 @@ def remove_client(self, client): # Remove their owner status due to single_cm pref. remove_owner will unlock the area if they were the last CM. if client in self._owners: self.remove_owner(client) - client.send_ooc( - "You can only be a CM of a single area in this hub.") + client.send_ooc("You can only be a CM of a single area in this hub.") if self.locking_allowed: # Since anyone can lock/unlock, unlock if we were the last client in this area and it was locked. if len(self.clients) - 1 <= 0: @@ -828,7 +820,7 @@ def remove_client(self, client): else: self.broadcast_player_list_to_target(client) - #Battle system + # Battle system if client in client.area.fighters: if client.area.battle_started: client.battle.current_client = None @@ -911,8 +903,7 @@ def unlink(self, target): try: del self.links[str(target)] except KeyError: - raise AreaError( - f"Link {target} does not exist in Area {self.name}!") + raise AreaError(f"Link {target} does not exist in Area {self.name}!") def is_char_available(self, char_id): """ @@ -927,9 +918,7 @@ def is_char_available(self, char_id): def get_rand_avail_char_id(self): """Get a random available character ID.""" - avail_set = set(range(len(self.area_manager.char_list))) - { - x.char_id for x in self.clients - } + avail_set = set(range(len(self.area_manager.char_list))) - {x.char_id for x in self.clients} if len(avail_set) == 0: raise AreaError("No available characters.") return random.choice(tuple(avail_set)) @@ -949,11 +938,7 @@ def send_owner_command(self, cmd, *args): for c in self.owners: if c in self.clients: continue - if ( - c.remote_listen == 3 - or (cmd == "CT" and c.remote_listen == 2) - or (cmd == "MS" and c.remote_listen == 1) - ): + if c.remote_listen == 3 or (cmd == "CT" and c.remote_listen == 2) or (cmd == "MS" and c.remote_listen == 1): c.send_command(cmd, *args) def send_owner_ic(self, bg, cmd, *args): @@ -981,9 +966,7 @@ def broadcast_ooc(self, msg): :param msg: message """ self.send_command("CT", self.server.config["hostname"], msg, "1") - self.send_owner_command( - "CT", f"[{self.id}]" + self.server.config["hostname"], msg, "1" - ) + self.send_owner_command("CT", f"[{self.id}]" + self.server.config["hostname"], msg, "1") def broadcast_action(self, client, msg): """ @@ -1002,51 +985,50 @@ def broadcast_action(self, client, msg): continue if not c.ooc_actions: continue - if ( - c.remote_listen == 3 - or c.remote_listen == 2 - ): + if c.remote_listen == 3 or c.remote_listen == 2: c.send_command(cmd, f"[{self.id}]" + self.server.config["hostname"], msg, "1") - def send_ic(self, - client=None, - msg_type="1", - pre=0, - folder="", - anim="", - msg="", - pos="", - sfx="", - emote_mod=0, - cid=-1, - sfx_delay=0, - button=0, - evidence=[0], - flip=0, - ding=0, - color=0, - showname="", - charid_pair=-1, - other_folder="", - other_emote="", - offset_pair=0, - other_offset=0, - other_flip=0, - nonint_pre=0, - sfx_looping="0", - screenshake=0, - frames_shake="", - frames_realization="", - frames_sfx="", - additive=0, - effect="", - targets=None, - third_charid=-1, - third_folder="", - third_emote=0, - third_offset="", - third_flip=0, - video=""): + def send_ic( + self, + client=None, + msg_type="1", + pre=0, + folder="", + anim="", + msg="", + pos="", + sfx="", + emote_mod=0, + cid=-1, + sfx_delay=0, + button=0, + evidence=[0], + flip=0, + ding=0, + color=0, + showname="", + charid_pair=-1, + other_folder="", + other_emote="", + offset_pair=0, + other_offset=0, + other_flip=0, + nonint_pre=0, + sfx_looping="0", + screenshake=0, + frames_shake="", + frames_realization="", + frames_sfx="", + additive=0, + effect="", + targets=None, + third_charid=-1, + third_folder="", + third_emote=0, + third_offset="", + third_flip=0, + video="", + ): """ Send an IC message from a client to all applicable clients in the area. :param client: speaker @@ -1062,22 +1044,17 @@ def send_ic(self, lst = list(self.testimony[idx]) lst[4] = "}}}" + msg[2:] self.testimony[idx] = tuple(lst) - self.broadcast_ooc( - f"{client.showname} has amended Statement {idx+1}.") + self.broadcast_ooc(f"{client.showname} has amended Statement {idx+1}.") if not self.recording: self.testimony_send(idx) except IndexError: - client.send_ooc( - f"Something went wrong, couldn't amend Statement {idx+1}!" - ) + client.send_ooc(f"Something went wrong, couldn't amend Statement {idx+1}!") return - adding = msg.strip( - ) != "" and self.recording and client is not None + adding = msg.strip() != "" and self.recording and client is not None if client and msg.startswith("++") and len(self.testimony) > 0: if len(self.testimony) >= 30: - client.send_ooc( - "Maximum testimony statement amount reached! (30)") + client.send_ooc("Maximum testimony statement amount reached! (30)") return adding = True elif client: @@ -1088,7 +1065,7 @@ def send_ic(self, target = "" # message contains an "at" sign aka we're referring to someone specific if "@" in lwr: - target = lwr[lwr.find("@") + 1:] + target = lwr[lwr.find("@") + 1 :] try: opponent = None target = target.lower() @@ -1119,8 +1096,7 @@ def send_ic(self, commands.ooc_cmd_concede(client, "") # Shouter provided target but no opponent was found elif target != "" or self.minigame in ["Cross Swords", "Scrum Debate"]: - raise AreaError( - "Interjection minigame - target not found!") + raise AreaError("Interjection minigame - target not found!") # Minigame didn't swap as a result of this shout, don't display the shout if self.minigame != "" and self.minigame == old_minigame: @@ -1177,23 +1153,17 @@ def send_ic(self, client.area.last_ic_message is not None and client.area.last_ic_message[8] == client.char_id and client.area.last_ic_message[16] != -1 - and int(client.area.last_ic_message[16].split("^")[0]) - in opposing_team + and int(client.area.last_ic_message[16].split("^")[0]) in opposing_team ): # Set the pair to the person who it was last msg - charid_pair = int( - client.area.last_ic_message[16].split("^")[0] - ) + charid_pair = int(client.area.last_ic_message[16].split("^")[0]) # The person we were trying to find is no longer on the opposing team else: # Search through the opposing team's characters for other_cid in opposing_team: charid_pair = other_cid # If last message's charid matches a member of this team, prioritize theirs - if ( - client.area.last_ic_message is not None - and other_cid == client.area.last_ic_message[8] - ): + if client.area.last_ic_message is not None and other_cid == client.area.last_ic_message[8]: break # If our pair opponent is found if charid_pair != -1: @@ -1220,8 +1190,7 @@ def send_ic(self, or pos != self.last_ic_message[8] or self.last_ic_message[4].strip() != "" ): - database.log_area("chat.ic", client, - client.area, message=msg) + database.log_area("chat.ic", client, client.area, message=msg) if targets is None: targets = self.clients @@ -1234,12 +1203,7 @@ def send_ic(self, continue # pos doesn't match listen_pos, we're not listening so make this an OOC message instead if c.area == self and c.listen_pos is not None: - if ( - type(c.listen_pos) is list - and pos not in c.listen_pos - or c.listen_pos == "self" - and pos != c.pos - ): + if type(c.listen_pos) is list and pos not in c.listen_pos or c.listen_pos == "self" and pos != c.pos: name = "" if cid != -1: name = self.area_manager.char_list[cid] @@ -1248,8 +1212,7 @@ def send_ic(self, # Send the mesage as OOC. # Woulda been nice if there was a packet to send messages to IC log # without displaying it in the viewport. - c.send_command( - "CT", f"[pos '{pos}'] {name}", msg) + c.send_command("CT", f"[pos '{pos}'] {name}", msg) continue # Before we send the message, if our remote_listen is different... @@ -1259,51 +1222,52 @@ def send_ic(self, msg_to_send = msg if c.area != self: msg_to_send = "}}}[" + str(self.id) + "] {{{" + msg - c.send_command("MS", msg_type, - pre, - folder, - # if we're in first person mode, treat our msgs as narration - "" if c == client and client.firstperson else anim, - msg_to_send, - pos, - sfx, - emote_mod, - cid, - sfx_delay, - button, - evidence, - flip, - ding, - color, - showname, - charid_pair, - other_folder, - other_emote, - offset_pair, - other_offset, - other_flip, - nonint_pre, - sfx_looping, - screenshake, - frames_shake, - frames_realization, - frames_sfx, - additive, - effect, - third_charid, - third_folder, - third_emote, - third_offset, - third_flip, - video) + c.send_command( + "MS", + msg_type, + pre, + folder, + # if we're in first person mode, treat our msgs as narration + "" if c == client and client.firstperson else anim, + msg_to_send, + pos, + sfx, + emote_mod, + cid, + sfx_delay, + button, + evidence, + flip, + ding, + color, + showname, + charid_pair, + other_folder, + other_emote, + offset_pair, + other_offset, + other_flip, + nonint_pre, + sfx_looping, + screenshake, + frames_shake, + frames_realization, + frames_sfx, + additive, + effect, + third_charid, + third_folder, + third_emote, + third_offset, + third_flip, + video, + ) if self.recording: # See if the testimony is supposed to end here. scrunched = "".join(e for e in msg if e.isalnum()) if len(scrunched) > 0 and scrunched.lower() == "end": self.recording = False - self.broadcast_ooc( - f"[{client.id}] {client.showname} has ended the testimony." - ) + self.broadcast_ooc(f"[{client.id}] {client.showname} has ended the testimony.") self.send_command("RT", "testimony1", 1) return if anim == "" or pos == "": @@ -1345,19 +1309,18 @@ def send_ic(self, frames_sfx, # 27 additive, # 28 effect, # 29 - third_charid, # 30 - third_folder, # 31 - third_emote, # 32 - third_offset, # 33 - third_flip, # 34 - video, #35 + third_charid, # 30 + third_folder, # 31 + third_emote, # 32 + third_offset, # 33 + third_flip, # 34 + video, # 35 ) self.last_ic_message = args if adding: if len(self.testimony) >= 30: - client.send_ooc( - "Maximum testimony statement amount reached! (30)") + client.send_ooc("Maximum testimony statement amount reached! (30)") return if msg.startswith("++"): msg = msg[2:] @@ -1405,12 +1368,12 @@ def send_ic(self, frames_sfx, # 27 additive, # 28 effect, # 29 - third_charid, # 30 - third_folder, # 31 - third_emote, # 32 - third_offset, # 33 - third_flip, # 34 - video, # 35 + third_charid, # 30 + third_folder, # 31 + third_emote, # 32 + third_offset, # 33 + third_flip, # 34 + video, # 35 ) if idx == -1: # Add one statement at the very end. @@ -1471,11 +1434,8 @@ def broadcast_player_list(self): def broadcast_player_list_to_target(self, target): return_data = {} - return_data['packet'] = 'player_list' - special_allowed = ( - target.is_mod - or target in self.owners - ) + return_data["packet"] = "player_list" + special_allowed = target.is_mod or target in self.owners player_data_to_send = list() player_stuff = list() if (self.can_getarea and not self.dark) or special_allowed: @@ -1491,15 +1451,14 @@ def broadcast_player_list_to_target(self, target): chara_client_info["id"] = str(c.id) chara_client_info["afk"] = str(c in self.afkers) - #Append the Showname + # Append the Showname # 1.5 player_stuff.append(str(c.showname)) chara_client_info["showname"] = str(c.showname) # 1.5.1 - - #Append the Character Name + # Append the Character Name # 1.5 # if(c.icon_visible): char_folder = "Spectator" @@ -1511,7 +1470,7 @@ def broadcast_player_list_to_target(self, target): # player_stuff.append("") # chara_client_info["character"] = "NO_CHARA" - if(target.is_mod): + if target.is_mod: # chara_client_info["HDID"] = str(c.hdid) chara_client_info["IPID"] = str(c.ipid) @@ -1519,16 +1478,16 @@ def broadcast_player_list_to_target(self, target): # chara_client_info["url"] = c.files[1] # if(c.char_outfit): - # chara_client_info["outfit"] = c.char_outfit + # chara_client_info["outfit"] = c.char_outfit - if(c.desc): + if c.desc: chara_client_info["status"] = c.desc player_data_to_send.append(chara_client_info) - return_data['data'] = player_data_to_send - + return_data["data"] = player_data_to_send + json_data = json.dumps(return_data) - target.send_command('JSN', json_data) - target.send_command('LP', player_stuff) + target.send_command("JSN", json_data) + target.send_command("LP", player_stuff) def parse_msg_delay(self, msg): """Just returns the delay value between messages. @@ -1550,7 +1509,7 @@ def is_iniswap(self, client, preanim, anim, char, sfx): client.iniswap = char else: client.iniswap = "" - + if self.iniswap_allowed: return False # Our client is narrating or blankposting via slash command @@ -1613,19 +1572,13 @@ def add_jukebox_vote(self, client, music_name, length=-1, showname=""): return if length == 0: self.remove_jukebox_vote(client, False) - if len(self.jukebox_votes) <= 1 or ( - not self.music_looper or self.music_looper.cancelled() - ): + if len(self.jukebox_votes) <= 1 or (not self.music_looper or self.music_looper.cancelled()): self.start_jukebox() else: self.remove_jukebox_vote(client, True) - self.jukebox_votes.append( - self.JukeboxVote(client, music_name, length, showname) - ) + self.jukebox_votes.append(self.JukeboxVote(client, music_name, length, showname)) client.send_ooc("Your song was added to the jukebox.") - if len(self.jukebox_votes) == 1 or ( - not self.music_looper or self.music_looper.cancelled() - ): + if len(self.jukebox_votes) == 1 or (not self.music_looper or self.music_looper.cancelled()): self.start_jukebox() def remove_jukebox_vote(self, client, silent): @@ -1652,21 +1605,14 @@ def get_jukebox_picked(self): song_list = self.server.music_list # Hub music list - if ( - self.area_manager.music_ref != "" - and len(self.area_manager.music_list) > 0 - ): + if self.area_manager.music_ref != "" and len(self.area_manager.music_list) > 0: if self.area_manager.replace_music: song_list = self.area_manager.music_list else: song_list = song_list + self.area_manager.music_list # Area music list - if ( - self.music_ref != "" - and self.music_ref != self.area_manager.music_ref - and len(self.music_list) > 0 - ): + if self.music_ref != "" and self.music_ref != self.area_manager.music_ref and len(self.music_list) > 0: if self.replace_music: song_list = self.music_list else: @@ -1677,11 +1623,9 @@ def get_jukebox_picked(self): if "category" in c: # Either play a completely random category, or play a category the last song was in if "songs" in c: - if self.music == "" or self.music in [ - b["name"] for b in c["songs"] - ]: + if self.music == "" or self.music in [b["name"] for b in c["songs"]]: for s in c["songs"]: - looping = ("length" not in s or s["length"] == -1) + looping = "length" not in s or s["length"] == -1 if not looping or s["name"] == self.music: continue songs = songs + [s] @@ -1711,10 +1655,7 @@ def start_jukebox(self): # we should check that. # We also do a check if we were the last to play a song, just in case. if not self.jukebox: - if ( - self.music_player == "The Jukebox" - and self.music_player_ipid == "has no IPID" - ): + if self.music_player == "The Jukebox" and self.music_player_ipid == "has no IPID": self.music = "" return @@ -1722,8 +1663,7 @@ def start_jukebox(self): if vote_picked is None: self.music = "" - self.send_command("MC", self.music, -1, "", 1, - 0, int(MusicEffect.FADE_OUT)) + self.send_command("MC", self.music, -1, "", 1, 0, int(MusicEffect.FADE_OUT)) return if vote_picked.name == self.music: @@ -1775,15 +1715,11 @@ def start_jukebox(self): else: current_vote.chance += 1 - length = ( - vote_picked.length - 3 - ) # Remove a few seconds to have a smooth fade out + length = vote_picked.length - 3 # Remove a few seconds to have a smooth fade out if length <= 0: # Length not defined length = 120.0 # Play each song for at least 2 minutes - self.music_looper = asyncio.get_running_loop().call_later( - max(5, length), lambda: self.start_jukebox() - ) + self.music_looper = asyncio.get_running_loop().call_later(max(5, length), lambda: self.start_jukebox()) def set_ambience(self, name): self.ambience = name @@ -1848,29 +1784,28 @@ def change_background_suffix(self, bg_suffix, mode=1): for client in self.clients: client.send_command("BN", self.background, client.pos, self.overlay, mode) - def change_background(self, bg, overlay="", mode=1): """ Set the background and/or overlay. - + parameters: bg: background name silent: should send the pre 2.8 packet or the new one? overlay: overlay name (optional) - + :raises: AreaError if `bg` is not in background list - + BN packet implementation: - + Before 2.8 (Changes after sending a IC message): BN # - + AO 2.8 (Clear viewport and update/change background position): BN # # - + AOG 1.0 (Put a additional image on top of the character): BN # # # # - + mode: 0 = pre 2.8 version (change background after IC message) 1 = 2.8 version (Change background immediately, clearing the viewport) 2 = Change background without clearing the viewport @@ -1878,7 +1813,7 @@ def change_background(self, bg, overlay="", mode=1): The client should be expected to implement at least the first two. - + """ if self.use_backgrounds_yaml: if len(self.server.backgrounds) <= 0: @@ -1925,9 +1860,7 @@ def change_status(self, value): False, ) if value.lower() == "hub": - raise AreaError( - 'Hub Status is a restricted value.' - ) + raise AreaError("Hub Status is a restricted value.") if value.lower() == "lfp": value = "looking-for-players" self.status = value.upper() @@ -2013,8 +1946,7 @@ def add_owner(self, client): # Update their judge buttons self.update_judge_buttons(client) - self.broadcast_ooc( - f"{client.showname} [{client.id}] is CM in this area now.") + self.broadcast_ooc(f"{client.showname} [{client.id}] is CM in this area now.") def remove_owner(self, client, dc=False): """ @@ -2049,9 +1981,7 @@ def remove_owner(self, client, dc=False): # Update their judge buttons self.update_judge_buttons(client) - self.broadcast_ooc( - f"{client.showname} [{client.id}] is no longer CM in this area." - ) + self.broadcast_ooc(f"{client.showname} [{client.id}] is no longer CM in this area.") def broadcast_area_list(self, client=None, refresh=False): """ @@ -2086,8 +2016,7 @@ def time_until_move(self, client): :return: time left until you can move again or 0. """ secs = round(time.time() * 1000.0 - client.last_move_time) - total = sum([client.move_delay, self.move_delay, - self.area_manager.move_delay]) + total = sum([client.move_delay, self.move_delay, self.area_manager.move_delay]) test = total * 1000.0 - secs if test > 0: return test @@ -2151,28 +2080,31 @@ def vote_end_minigame(self, client): return valid_voters = [ - c for c in self.clients if - not c.hidden and - c not in self.afkers and - c not in self.owners and - c.char_id not in client.area.blue_team and - c.char_id not in client.area.red_team + c + for c in self.clients + if not c.hidden + and c not in self.afkers + and c not in self.owners + and c.char_id not in client.area.blue_team + and c.char_id not in client.area.red_team ] if client not in valid_voters: - client.send_ooc("You're not qualified to vote-end this minigame! (You're a Spectator, Hidden or the area owner)") + client.send_ooc( + "You're not qualified to vote-end this minigame! (You're a Spectator, Hidden or the area owner)" + ) return self.votes_cast.add(client) votes_casted = len(self.votes_cast) votes_needed = round(len(valid_voters) * self.votes_percentage) - info = f'[{client.id}] {client.showname} is voting to end the minigame!' + info = f"[{client.id}] {client.showname} is voting to end the minigame!" if votes_casted >= votes_needed: client.area.end_minigame("Voted to end.") - info += f'\nSuccessfully voted to end with ({votes_casted}/{votes_needed}) votes.' + info += f"\nSuccessfully voted to end with ({votes_casted}/{votes_needed}) votes." else: - info += f'({votes_casted}/{votes_needed}) votes left.' - + info += f"({votes_casted}/{votes_needed}) votes left." + self.broadcast_ooc(info) def start_debate(self, client, target, pta=False): @@ -2206,9 +2138,7 @@ def start_debate(self, client, target, pta=False): self.broadcast_ooc("🔴Red team conceded!") self.end_minigame("~Red~ team conceded!") return - self.broadcast_ooc( - f"[{client.id}] {client.showname} is now part of the {team} team!" - ) + self.broadcast_ooc(f"[{client.id}] {client.showname} is now part of the {team} team!") database.log_area( "minigame.sd", client, @@ -2219,8 +2149,7 @@ def start_debate(self, client, target, pta=False): return elif self.minigame == "Cross Swords": if target == client: - self.broadcast_ooc( - f"[{client.id}] {client.showname} conceded!") + self.broadcast_ooc(f"[{client.id}] {client.showname} conceded!") self.end_minigame(f"[{client.id}] {client.showname} conceded!") return if not self.can_scrum_debate: @@ -2241,9 +2170,7 @@ def start_debate(self, client, target, pta=False): self.minigame_schedule.cancel() self.minigame = "Scrum Debate" timer = timeleft + self.scrum_debate_added_time - self.broadcast_ooc( - f"[{client.id}] {client.showname} is now part of the {team} team!" - ) + self.broadcast_ooc(f"[{client.id}] {client.showname} is now part of the {team} team!") database.log_area( "minigame.sd", client, @@ -2258,8 +2185,7 @@ def start_debate(self, client, target, pta=False): if pta and not self.can_panic_talk_action: raise AreaError("You may not PTA in this area!") if client == target: - raise AreaError( - "You cannot initiate a minigame against yourself!") + raise AreaError("You cannot initiate a minigame against yourself!") self.old_invite_list = self.invite_list self.old_muted = self.muted @@ -2272,7 +2198,7 @@ def start_debate(self, client, target, pta=False): self.blue_team.clear() self.red_team.add(client.char_id) self.blue_team.add(target.char_id) - + self.votes_cast.clear() if pta: self.minigame = "Panic Talk Action" @@ -2298,12 +2224,10 @@ def start_debate(self, client, target, pta=False): song = self.cross_swords_song_start else: if target == client: - self.broadcast_ooc( - f"[{client.id}] {client.showname} conceded!") + self.broadcast_ooc(f"[{client.id}] {client.showname} conceded!") self.end_minigame(f"[{client.id}] {client.showname} conceded!") return - raise AreaError( - f"{self.minigame} is happening! You cannot interrupt it.") + raise AreaError(f"{self.minigame} is happening! You cannot interrupt it.") timer = max(5, int(timer)) # Timer ID 2 is used, start it @@ -2345,8 +2269,7 @@ def play_demo(self, client): self.stop_demo() return if client not in self.owners: - client.send_ooc( - "[Demo] Playback stopped due to you having insufficient permissions! (Not CM/GM anymore)") + client.send_ooc("[Demo] Playback stopped due to you having insufficient permissions! (Not CM/GM anymore)") self.stop_demo() return @@ -2356,9 +2279,7 @@ def play_demo(self, client): # It's a wait packet if header == "wait": secs = float(args[0]) / 1000 - self.demo_schedule = asyncio.get_running_loop().call_later( - secs, lambda: self.play_demo(client) - ) + self.demo_schedule = asyncio.get_running_loop().call_later(secs, lambda: self.play_demo(client)) return if header.startswith("/"): # It's a command call # TODO: make this into a global function so commands can be called from anywhere in code... @@ -2368,17 +2289,11 @@ def play_demo(self, client): arg = " ".join(args)[:1024] try: called_function = f"ooc_cmd_{cmd}" - if len(client.server.command_aliases) > 0 and not hasattr( - commands, called_function - ): + if len(client.server.command_aliases) > 0 and not hasattr(commands, called_function): if cmd in client.server.command_aliases: - called_function = ( - f"ooc_cmd_{client.server.command_aliases[cmd]}" - ) + called_function = f"ooc_cmd_{client.server.command_aliases[cmd]}" if not hasattr(commands, called_function): - client.send_ooc( - f"[Demo] Invalid command: {cmd}. Use /help to find up-to-date commands." - ) + client.send_ooc(f"[Demo] Invalid command: {cmd}. Use /help to find up-to-date commands.") self.stop_demo() return getattr(commands, called_function)(client, arg) diff --git a/server/area_manager.py b/server/area_manager.py index 994af49a..ede0ea1c 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -130,7 +130,7 @@ def __init__(self, hub_manager, name): self.time_of_day = "" self.timer = self.Timer() - + # RPS-5 rules as default self.rps_rules = [ ["rock", "scissors", "lizard"], @@ -149,8 +149,7 @@ def name(self): def name(self, value): self._name = value.strip() while "" in self._name or "" in self._name: - self._name = self._name.replace( - "", "").replace("", "") + self._name = self._name.replace("", "").replace("", "") self.abbreviation = self.abbreviate() @property @@ -334,8 +333,7 @@ def clear_music(self): def load_music(self, path): try: if not os.path.isfile(path): - raise AreaError( - f"Hub {self.name} trying to load music list: File path {path} is invalid!") + raise AreaError(f"Hub {self.name} trying to load music list: File path {path} is invalid!") with open(path, "r", encoding="utf-8") as stream: music_list = yaml.safe_load(stream) @@ -371,8 +369,7 @@ def load_character_data(self, path="config/character_data.yaml"): with open(path, "r") as chars: data = yaml.safe_load(chars) except Exception: - raise AreaError( - f"Hub {self.name} trying to load character data: File path {path} is invalid!") + raise AreaError(f"Hub {self.name} trying to load character data: File path {path} is invalid!") try: for char in data.copy(): @@ -381,8 +378,7 @@ def load_character_data(self, path="config/character_data.yaml"): data[self.char_list[char]] = data.pop(char) self.character_data = data except Exception: - raise AreaError( - "Something went wrong while loading the character data!") + raise AreaError("Something went wrong while loading the character data!") def save_character_data(self, path="config/character_data.yaml"): """ @@ -392,11 +388,9 @@ def save_character_data(self, path="config/character_data.yaml"): """ try: with open(path, "w", encoding="utf-8") as stream: - yaml.dump(self.character_data, stream, - default_flow_style=False) + yaml.dump(self.character_data, stream, default_flow_style=False) except Exception: - raise AreaError( - f"Hub {self.name} trying to save character data: File path {path} is invalid!") + raise AreaError(f"Hub {self.name} trying to save character data: File path {path} is invalid!") def get_character_data(self, char, key, default_value=None): """ @@ -518,9 +512,7 @@ def add_owner(self, client): client.area.broadcast_area_list(client) client.area.broadcast_evidence_list() - self.broadcast_ooc( - f"[{client.id}] {client.showname} ({client.name}) is GM in this hub now." - ) + self.broadcast_ooc(f"[{client.id}] {client.showname} ({client.name}) is GM in this hub now.") client.hide(True) def remove_owner(self, client): @@ -541,9 +533,7 @@ def remove_owner(self, client): client.area.broadcast_area_list(client) client.area.broadcast_evidence_list() - self.broadcast_ooc( - f"[{client.id}] {client.showname} ({client.name}) is no longer GM in this hub." - ) + self.broadcast_ooc(f"[{client.id}] {client.showname} ({client.name}) is no longer GM in this hub.") client.hide(False) def get_gms(self): @@ -596,17 +586,15 @@ def get_areas_by_args(self, args): # MAKE SURE TO RETURN A COPY AND NOT A DIRECT REFERENCE OR YOU'RE GONNA BE IN A WORLD OF HURT return self.areas.copy() # If arg is area range, e.g. 1-12, extend list by that range - a_range = arg.split('-') + a_range = arg.split("-") if len(a_range) == 2 and a_range[0].strip().isnumeric() and a_range[1].strip().isnumeric(): # unpack the range, turn it into a list and add it to the arglist - arg_list += [*range(int(a_range[0]), int(a_range[1])+1)] + arg_list += [*range(int(a_range[0]), int(a_range[1]) + 1)] continue # Otherwise, just append the arg arg_list.append(arg.lower()) for area in self.areas: - if area.id in arg_list or \ - str(area.id) in arg_list or \ - area.name.lower() in arg_list: + if area.id in arg_list or str(area.id) in arg_list or area.name.lower() in arg_list: area_list.append(area) return area_list @@ -649,7 +637,7 @@ def update_subtheme(self, client): client.send_command("GM", self.subtheme) # Set the time of day as well client.send_command("TOD", self.time_of_day) - + # AO portion if client.subtheme != self.subtheme or client.time_of_day != self.time_of_day: # ST is Subtheme, AO lacks time of day equivalent. @@ -659,7 +647,7 @@ def update_subtheme(self, client): else: client.send_command("ST", self.subtheme, "1") client.subtheme = self.subtheme - + def broadcast_subtheme(self): for client in self.clients: self.update_subtheme(client) @@ -677,17 +665,12 @@ def send_arup_players(self, clients=None): for area in client.local_area_list: playercount = -1 if not self.hide_clients and not area.hide_clients: - playercount = len( - [c for c in area.clients if not c.hidden]) + playercount = len([c for c in area.clients if not c.hidden]) players_list.append(playercount) playerhubcount = 0 for area in client.local_area_list: for c in area.clients: - if ( - not self.hide_clients - and not area.hide_clients - and not c.hidden - ): + if not self.hide_clients and not area.hide_clients and not c.hidden: playerhubcount = playerhubcount + 1 if len(self.server.hub_manager.hubs) > 1: players_list[1] = playerhubcount diff --git a/server/client_manager.py b/server/client_manager.py index fef2e80c..3dc83c57 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -81,27 +81,20 @@ def __init__(self, server, transport, user_id, ipid): self.mus_counter = 0 self.mus_mute_time = 0 self.mus_change_time = [ - x * - self.server.config["music_change_floodguard"]["interval_length"] - for x in range( - self.server.config["music_change_floodguard"]["times_per_interval"] - ) + x * self.server.config["music_change_floodguard"]["interval_length"] + for x in range(self.server.config["music_change_floodguard"]["times_per_interval"]) ] self.wtce_counter = 0 self.wtce_mute_time = 0 self.wtce_time = [ x * self.server.config["wtce_floodguard"]["interval_length"] - for x in range( - self.server.config["wtce_floodguard"]["times_per_interval"] - ) + for x in range(self.server.config["wtce_floodguard"]["times_per_interval"]) ] self.ooc_counter = 0 self.ooc_mute_time = 0 self.ooc_time = [ x * self.server.config["ooc_floodguard"]["interval_length"] - for x in range( - self.server.config["ooc_floodguard"]["times_per_interval"] - ) + for x in range(self.server.config["ooc_floodguard"]["times_per_interval"]) ] # security stuff self.clientscon = 0 @@ -182,16 +175,16 @@ def __init__(self, server, transport, user_id, ipid): # The currently playing audio for this client. Keeping track so we don't replay the same audio erroneously # (such as in the case of music_autoplay areas) self.playing_audio = ["", ""] - + # rainbowtext hell self.rainbow = False - + # rock paper scissors choice self.rps_choice = "" # Battle system stuff self.battle = None - + # If only the available areas should be displayed even as a GM self.available_areas_only = False @@ -256,28 +249,28 @@ def send_command(self, command, *args): charid_pair = str(args[16]) self_offset_x = 0 if len(args) > 19 and args[19]: - offset = str(args[19]).replace('', '&').split('&') + offset = str(args[19]).replace("", "&").split("&") self_offset_x = offset[0] if len(offset) > 1: offset[1] offset_pair_x = 0 if len(args) > 20 and args[20]: - offset = str(args[20]).replace('', '&').split('&') + offset = str(args[20]).replace("", "&").split("&") offset_pair_x = offset[0] if len(offset) > 1: offset[1] self_offset_x_dro = 500 if self_offset_x: - self_offset_x_dro = int((float(self_offset_x) / 100) * 960 + 480) # offset_pair + self_offset_x_dro = int((float(self_offset_x) / 100) * 960 + 480) # offset_pair # self_offset_y_dro = 0 # if self_offset_y: # self_offset_y_dro = int((float(self_offset_y) / 100) * 960 + 480) # offset_pair # Pair data detected! if (charid_pair and charid_pair != "-1") or (self_offset_x and self_offset_x != "0"): pair_jsn_packet = {} - pair_jsn_packet['packet'] = 'pair_data' - pair_jsn_packet['data'] = {} + pair_jsn_packet["packet"] = "pair_data" + pair_jsn_packet["data"] = {} other_emote = "" other_folder = "" other_flip = False @@ -287,31 +280,31 @@ def send_command(self, command, *args): other_emote = args[18] if len(args) > 21: other_flip = bool(int(args[21])) - pair_jsn_packet['data']['character'] = other_folder - pair_jsn_packet['data']['last_sprite'] = other_emote - pair_jsn_packet['data']['flipped'] = other_flip - + pair_jsn_packet["data"]["character"] = other_folder + pair_jsn_packet["data"]["last_sprite"] = other_emote + pair_jsn_packet["data"]["flipped"] = other_flip + # no y offset is supported and on DRO Client, the pairing offsets are measured in pixels rather than percentage offset_pair_x_dro = 500 if offset_pair_x: - offset_pair_x_dro = int((float(offset_pair_x) / 100) * 960 + 480) # other_offset - pair_jsn_packet['data']['self_offset'] = self_offset_x_dro - pair_jsn_packet['data']['offset_pair'] = offset_pair_x_dro - + offset_pair_x_dro = int((float(offset_pair_x) / 100) * 960 + 480) # other_offset + pair_jsn_packet["data"]["self_offset"] = self_offset_x_dro + pair_jsn_packet["data"]["offset_pair"] = offset_pair_x_dro + # Send the result! json_data = json.dumps(pair_jsn_packet) - self.send_command('JSN', json_data) + self.send_command("JSN", json_data) # No pair :( else: pair_jsn_packet = {} - pair_jsn_packet['packet'] = 'pair' - pair_jsn_packet['data'] = {} - pair_jsn_packet['data']['pair_left'] = -1 - pair_jsn_packet['data']['pair_right'] = -1 - pair_jsn_packet['data']['offset_left'] = 0 - pair_jsn_packet['data']['offset_right'] = 0 + pair_jsn_packet["packet"] = "pair" + pair_jsn_packet["data"] = {} + pair_jsn_packet["data"]["pair_left"] = -1 + pair_jsn_packet["data"]["pair_right"] = -1 + pair_jsn_packet["data"]["offset_left"] = 0 + pair_jsn_packet["data"]["offset_right"] = 0 json_data = json.dumps(pair_jsn_packet) - self.send_command('JSN', json_data) + self.send_command("JSN", json_data) # Now, modify the packet lst = list(args) # make sure to pad the list out @@ -319,7 +312,7 @@ def send_command(self, command, *args): # append with 0s we're gonna replace anyway lst.append(0) lst[16] = "" # No video support :( - lst[17] = hide_char # hide character if we're blankposting or narrating + lst[17] = hide_char # hide character if we're blankposting or narrating lst[18] = -1 # would be target id, but we dunno who lst[19] = self_offset_x_dro # offset_h lst[20] = 0 # offset_v @@ -353,9 +346,7 @@ def send_hub_info(self): """Send the hub info to the client.""" info = self.area.area_manager.info if info != "": - self.send_ooc( - f"🌍HUB [{self.area.area_manager.id}] {self.area.area_manager.name} INFO🌍\r\n{info}\r\n" - ) + self.send_ooc(f"🌍HUB [{self.area.area_manager.id}] {self.area.area_manager.name} INFO🌍\r\n{info}\r\n") def send_player_count(self): """ @@ -370,7 +361,7 @@ def send_timer_set_time(self, timer_id=None, new_time=None, start=False): if timer_id == 0: timer = self.area.area_manager.timer else: - timer = self.area.timers[timer_id-1] + timer = self.area.timers[timer_id - 1] if self.software == "DRO": # configuration. There's no situation where these values are different on KFO-Server # step length cannot be manually modified yet @@ -381,16 +372,16 @@ def send_timer_set_time(self, timer_id=None, new_time=None, start=False): # as of 1.8.1, set timer format self.send_command("TSR", timer_id, timer.format) if start: - self.send_command("TR", timer_id) # resume + self.send_command("TR", timer_id) # resume else: - self.send_command("TP", timer_id) # pause + self.send_command("TP", timer_id) # pause else: if new_time is None: - self.send_command("TI", timer_id, 1, 0) # Stop timer - self.send_command("TI", timer_id, 3, 0) # Hide timer + self.send_command("TI", timer_id, 1, 0) # Stop timer + self.send_command("TI", timer_id, 3, 0) # Hide timer else: - self.send_command("TI", timer_id, 2, new_time) # Show timer - self.send_command("TI", timer_id, int(not start), new_time) # Set timer with value and start + self.send_command("TI", timer_id, 2, new_time) # Show timer + self.send_command("TI", timer_id, int(not start), new_time) # Set timer with value and start self.send_command("TF", timer_id, timer.format, new_time) self.send_command("TIN", timer_id, timer.interval) @@ -401,19 +392,19 @@ def send_timer_set_interval(self, timer_id, timer): else: current_time = int(timer.static.total_seconds()) * 1000 if timer_id == 0: - self.area.area_manager.send_timer_set_time(timer_id, current_time, timer.started) + self.area.area_manager.send_timer_set_time(timer_id, current_time, timer.started) else: - self.area.send_timer_set_time(timer_id, current_time, timer.started) + self.area.send_timer_set_time(timer_id, current_time, timer.started) def send_timer_set_step_length(self, timer_id=None, new_step_length=None): if self.software == "DRO": - self.send_command("TSS", timer_id, new_step_length) # set step + self.send_command("TSS", timer_id, new_step_length) # set step else: - pass # no ao equivalent + pass # no ao equivalent def send_timer_set_firing_interval(self, timer_id=None, new_firing_interval=None): if self.software == "DRO": - self.send_command("TSF", timer_id, new_firing_interval) #set firing + self.send_command("TSF", timer_id, new_firing_interval) # set firing else: self.send_command("TIN", timer_id, new_firing_interval) @@ -440,17 +431,20 @@ def disconnect(self): def record_latest_area(self): """Record the client character's latest area if not in lobby, if not spectator and not GM/mod.""" - if self.area.area_manager.default_area() != self.area and self.char_id != -1 and self not in self.area.owners and not self.is_mod: + if ( + self.area.area_manager.default_area() != self.area + and self.char_id != -1 + and self not in self.area.owners + and not self.is_mod + ): self.area.area_manager.set_character_data(self.char_id, "latest_area", self.area.id) - + def kick_to_latest_area(self): """Kick the client to their character's latest recorded area.""" - latest_area = self.area.area_manager.get_character_data( - self.char_id, "latest_area", None - ) + latest_area = self.area.area_manager.get_character_data(self.char_id, "latest_area", None) if latest_area is None: return - + target_area = self.area.area_manager.get_area_by_id(latest_area) if not target_area: return @@ -514,8 +508,7 @@ def change_character(self, char_id, force=False): raise ClientError("Cannot spectate in this hub!") raise ClientError("Cannot spectate in this area!") old_char = self.char_name - arup = (self.char_id == -1 or char_id == - - 1) and self.char_id != char_id + arup = (self.char_id == -1 or char_id == -1) and self.char_id != char_id self.char_id = char_id self.pos = "" self.send_command("PV", self.id, "CID", self.char_id) @@ -550,17 +543,17 @@ def change_character(self, char_id, force=False): if self.area.area_manager.autokick_to_latest_area: self.kick_to_latest_area() - else: + else: # Record last known area ID for this character if not spectator/gm/mod, and hub autokick is not set self.record_latest_area() self.area.update_timers(self, running_only=True) - + if not self.hidden and not self.sneaking: self.area.broadcast_player_list() else: self.area.broadcast_player_list_to_target(self) - self.area.broadcast_area_desc_to_target(self) + self.area.broadcast_area_desc_to_target(self) def change_music_cd(self): """ @@ -577,26 +570,16 @@ def change_music_cd(self): return 0 if self.mus_mute_time: - if ( - time.time() - self.mus_mute_time - < self.server.config["music_change_floodguard"]["mute_length"] - ): - return self.server.config["music_change_floodguard"][ - "mute_length" - ] - (time.time() - self.mus_mute_time) + if time.time() - self.mus_mute_time < self.server.config["music_change_floodguard"]["mute_length"]: + return self.server.config["music_change_floodguard"]["mute_length"] - ( + time.time() - self.mus_mute_time + ) else: self.mus_mute_time = 0 - times_per_interval = self.server.config["music_change_floodguard"][ - "times_per_interval" - ] - interval_length = self.server.config["music_change_floodguard"][ - "interval_length" - ] + times_per_interval = self.server.config["music_change_floodguard"]["times_per_interval"] + interval_length = self.server.config["music_change_floodguard"]["interval_length"] if ( - time.time() - - self.mus_change_time[ - (self.mus_counter - times_per_interval + 1) % times_per_interval - ] + time.time() - self.mus_change_time[(self.mus_counter - times_per_interval + 1) % times_per_interval] < interval_length ): self.mus_mute_time = time.time() @@ -616,20 +599,17 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): return # Decode AO packet - song = song.replace("", "#") \ - .replace("", "%") \ - .replace("", "$") \ - .replace("", "&") + song = song.replace("", "#").replace("", "%").replace("", "$").replace("", "&") try: - if song == "~stop.mp3" or song.strip() == "" or self.server.get_song_is_category( - self.construct_music_list(), song + if ( + song == "~stop.mp3" + or song.strip() == "" + or self.server.get_song_is_category(self.construct_music_list(), song) ): name, length = "~stop.mp3", 0 else: try: - name, length = self.server.get_song_data( - self.construct_music_list(), song - ) + name, length = self.server.get_song_data(self.construct_music_list(), song) except ServerError: if self.is_mod or self in self.area.owners: name = song @@ -639,7 +619,7 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): if not loop: length = 0 - if (contains_URL(song)): + if contains_URL(song): checked = False # Only if url music is configured to be allowed if self.server.config["music_allow_url"]: @@ -649,18 +629,13 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): if song.startswith(line): checked = True if not checked: - self.send_ooc( - "This URL is not allowed." - ) + self.send_ooc("This URL is not allowed.") return target_areas = [self.area] - if len(self.broadcast_list) > 0 and ( - self.is_mod or self in self.area.owners - ): + if len(self.broadcast_list) > 0 and (self.is_mod or self in self.area.owners): try: - a_list = ", ".join([str(a.id) - for a in self.broadcast_list]) + a_list = ", ".join([str(a.id) for a in self.broadcast_list]) self.send_ooc(f"Broadcasting to areas {a_list}") target_areas = self.broadcast_list except (AreaError, ValueError): @@ -676,21 +651,15 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): ) continue if not self.is_mod and self not in area.owners and not area.can_dj: - self.send_ooc( - f"You cannot change music in area [{area.id}] {area.name}!" - ) + self.send_ooc(f"You cannot change music in area [{area.id}] {area.name}!") continue if area.music_locked: - self.send_ooc( - f"You cannot change music in area [{area.id}] {area.name} - music is locked!" - ) + self.send_ooc(f"You cannot change music in area [{area.id}] {area.name} - music is locked!") continue if self.edit_ambience: if self.is_mod or self in area.owners: area.set_ambience(name) - self.send_ooc( - f"Setting area [{area.id}] {area.name} ambience to {name}." - ) + self.send_ooc(f"Setting area [{area.id}] {area.name} ambience to {name}.") continue else: self.edit_ambinece = False @@ -725,9 +694,7 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): self.area.panic_talk_action_song_end = song elif self.editing_minigame_song_condition == 2: self.area.panic_talk_action_song_concede = song - self.send_ooc( - f"Setting the {self.editing_minigame_song} {condition_str} song to {song}." - ) + self.send_ooc(f"Setting the {self.editing_minigame_song} {condition_str} song to {song}.") self.editing_minigame_song = "" self.editing_minigame_song_condition = 0 continue @@ -743,9 +710,7 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): and not self.is_mod and self not in area.owners ): - self.send_ooc( - f"Showname changes are forbidden in area [{area.id}] {area.name}!" - ) + self.send_ooc(f"Showname changes are forbidden in area [{area.id}] {area.name}!") continue # Effects info @@ -754,16 +719,14 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): # Jukebox check if area.jukebox and not self.is_mod and self not in area.owners: area.add_jukebox_vote(self, name, length, showname) - database.log_area( - "jukebox.vote", self, area, message=name) + database.log_area("jukebox.vote", self, area, message=name) else: if self.change_music_cd(): self.send_ooc( f"You changed song too many times. Please try again after {int(self.change_music_cd())} seconds." ) return - area.play_music(name, self.char_id, - length, showname, effects) + area.play_music(name, self.char_id, length, showname, effects) area.add_music_playing(self, name, showname) # Broadcast for the rest for a in area.broadcast_list: @@ -776,8 +739,7 @@ def change_music(self, song, cid, showname="", effects=0, loop=True): f"Error: song {song} was not accepted! View acceptable music by resetting your client's using /musiclist." ) else: - self.send_ooc( - f"Error: song {song} was not accepted! (No permission)") + self.send_ooc(f"Error: song {song} was not accepted! (No permission)") def wtce_mute(self): """ @@ -787,24 +749,14 @@ def wtce_mute(self): if self.is_mod or self in self.area.owners: return 0 if self.wtce_mute_time: - if ( - time.time() - self.wtce_mute_time - < self.server.config["wtce_floodguard"]["mute_length"] - ): - return self.server.config["wtce_floodguard"]["mute_length"] - ( - time.time() - self.wtce_mute_time - ) + if time.time() - self.wtce_mute_time < self.server.config["wtce_floodguard"]["mute_length"]: + return self.server.config["wtce_floodguard"]["mute_length"] - (time.time() - self.wtce_mute_time) else: self.wtce_mute_time = 0 - times_per_interval = self.server.config["wtce_floodguard"][ - "times_per_interval" - ] + times_per_interval = self.server.config["wtce_floodguard"]["times_per_interval"] interval_length = self.server.config["wtce_floodguard"]["interval_length"] if ( - time.time() - - self.wtce_time[ - (self.wtce_counter - times_per_interval + 1) % times_per_interval - ] + time.time() - self.wtce_time[(self.wtce_counter - times_per_interval + 1) % times_per_interval] < interval_length ): self.wtce_mute_time = time.time() @@ -812,7 +764,7 @@ def wtce_mute(self): self.wtce_counter = (self.wtce_counter + 1) % times_per_interval self.wtce_time[self.wtce_counter] = time.time() return 0 - + def ooc_mute(self): """ Check if the client can use OOC or not. @@ -821,24 +773,14 @@ def ooc_mute(self): if self.is_mod or self in self.area.owners: return 0 if self.ooc_mute_time: - if ( - time.time() - self.ooc_mute_time - < self.server.config["ooc_floodguard"]["mute_length"] - ): - return self.server.config["ooc_floodguard"]["mute_length"] - ( - time.time() - self.ooc_mute_time - ) + if time.time() - self.ooc_mute_time < self.server.config["ooc_floodguard"]["mute_length"]: + return self.server.config["ooc_floodguard"]["mute_length"] - (time.time() - self.ooc_mute_time) else: self.ooc_mute_time = 0 - times_per_interval = self.server.config["ooc_floodguard"][ - "times_per_interval" - ] + times_per_interval = self.server.config["ooc_floodguard"]["times_per_interval"] interval_length = self.server.config["ooc_floodguard"]["interval_length"] if ( - time.time() - - self.ooc_time[ - (self.ooc_counter - times_per_interval + 1) % times_per_interval - ] + time.time() - self.ooc_time[(self.ooc_counter - times_per_interval + 1) % times_per_interval] < interval_length ): self.ooc_mute_time = time.time() @@ -867,12 +809,8 @@ def load_music(self, path): prepath = "" for item in music_list: - if ( - "use_unique_folder" in item - and item["use_unique_folder"] is True - ): - prepath = os.path.splitext( - os.path.basename(path))[0] + "/" + if "use_unique_folder" in item and item["use_unique_folder"] is True: + prepath = os.path.splitext(os.path.basename(path))[0] + "/" if "category" not in item: continue @@ -894,10 +832,7 @@ def construct_music_list(self): song_list = self.server.music_list # Hub music list - if ( - self.area.area_manager.music_ref != "" - and len(self.area.area_manager.music_list) > 0 - ): + if self.area.area_manager.music_ref != "" and len(self.area.area_manager.music_list) > 0: if self.area.area_manager.replace_music: song_list = self.area.area_manager.music_list else: @@ -964,9 +899,7 @@ def reload_area_list(self, areas=[]): f"🌍[{self.area.area_manager.id}] {self.area.area_manager.name}\n Double-Click me to see Hubs\n _______" ] else: - area_list = [ - f"🌍[{self.area.area_manager.id}] {self.area.area_manager.name}" - ] + area_list = [f"🌍[{self.area.area_manager.id}] {self.area.area_manager.name}"] if len(areas) > 0: # This is where we can handle all the 'rendering', such as extra info etc. for area in areas: @@ -1005,7 +938,10 @@ def set_area(self, area, target_pos=""): # When swapping between hubs, if the new hub charlist is different from ours or if the target hub has character autokick enabled, # bring up the character select screen for the client - if old_area.area_manager != area.area_manager and (area.area_manager.autokick_to_latest_area or old_area.area_manager.char_list != area.area_manager.char_list): + if old_area.area_manager != area.area_manager and ( + area.area_manager.autokick_to_latest_area + or old_area.area_manager.char_list != area.area_manager.char_list + ): # Send them that hub's char list self.area.area_manager.send_characters(self) self.char_select() @@ -1045,10 +981,7 @@ def set_area(self, area, target_pos=""): "FA", *[ "🌐 Hubs 🌐\n Double-Click me to see Areas\n _______", - *[ - f"[{hub.id}] {hub.name} (users: {hub.count})" - for hub in self.server.hub_manager.hubs - ], + *[f"[{hub.id}] {hub.name} (users: {hub.count})" for hub in self.server.hub_manager.hubs], ], ) @@ -1082,11 +1015,11 @@ def set_area(self, area, target_pos=""): try: area_clients = self.get_area_clients(self.area.id) if area_clients != "": - msg += f'\nClients in area:{area_clients}' + msg += f"\nClients in area:{area_clients}" except ClientError as ex: - msg += f'\n{ex}' + msg += f"\n{ex}" self.send_ooc(msg) - + # DRO Client exclusive self.area.broadcast_area_desc_to_target(self) self.send_command("joined_area") @@ -1116,11 +1049,7 @@ def can_access_area(self, area): def try_access_area(self, area, peek=False): if self.frozen: raise ClientError("You are frozen in place so you can't move!") - if ( - self.area.locked - and self not in self.area.owners - and self.id not in self.area.invite_list - ): + if self.area.locked and self not in self.area.owners and self.id not in self.area.invite_list: raise ClientError("Current area is locked!") if len(self.area.links) > 0: @@ -1144,13 +1073,7 @@ def try_access_area(self, area, peek=False): raise ClientError("Area is locked!") if area.max_players > 0: - players = len( - [ - x - for x in area.clients - if (x not in area.owners and not x.is_mod and not x.hidden) - ] - ) + players = len([x for x in area.clients if (x not in area.owners and not x.is_mod and not x.hidden)]) if players >= area.max_players: raise ClientError("Area is full!") elif area.max_players == 0: @@ -1162,30 +1085,18 @@ def change_area(self, area, password=""): :param area: area to switch to """ if self.area == area: - raise ClientError( - f"Failed to enter [{area.id}] {area.name}: User already in specified area." - ) + raise ClientError(f"Failed to enter [{area.id}] {area.name}: User already in specified area.") allowed = ( - self.is_mod - or self in area.owners - or self.char_id == -1 - or area == area.area_manager.default_area() + self.is_mod or self in area.owners or self.char_id == -1 or area == area.area_manager.default_area() ) if not allowed: # If they're forced to follow, no escape. - if ( - self.forced_to_follow - and self.following is not None - and self.following.area != area - ): - raise ClientError( - "You can't escape when you've been forced to follow someone!" - ) + if self.forced_to_follow and self.following is not None and self.following.area != area: + raise ClientError("You can't escape when you've been forced to follow someone!") try: self.try_access_area(area) except ClientError as ex: - self.send_ooc( - f"Failed to enter [{area.id}] {area.name}: {ex}") + self.send_ooc(f"Failed to enter [{area.id}] {area.name}: {ex}") return if (area.password != "" and password != area.password) or ( @@ -1205,12 +1116,8 @@ def change_area(self, area, password=""): and self not in area.owners ): if not area.area_manager.can_spectate: - raise ClientError( - f"Failed to enter [{area.id}] {area.name}: Cannot spectate in this hub!" - ) - raise ClientError( - f"Failed to enter [{area.id}] {area.name}: Cannot spectate that area!" - ) + raise ClientError(f"Failed to enter [{area.id}] {area.name}: Cannot spectate in this hub!") + raise ClientError(f"Failed to enter [{area.id}] {area.name}: Cannot spectate that area!") target_pos = "" if len(self.area.links) > 0: @@ -1227,9 +1134,7 @@ def change_area(self, area, password=""): # You gotta unhide first lol self.hide(False) self.area.broadcast_area_list(self) - raise ClientError( - f"Failed to enter [{area.id}] {area.name}: You had to leave your hiding spot!" - ) + raise ClientError(f"Failed to enter [{area.id}] {area.name}: You had to leave your hiding spot!") delay = self.area.time_until_move(self) if not self.forced_to_follow and not allowed and delay > 0: @@ -1239,11 +1144,11 @@ def change_area(self, area, password=""): ) # Mods and area owners can be any character regardless of availability - if not ( - self.is_mod or self in area.owners or self.char_id == -1 - ) and not area.is_char_available(self.char_id): + if not (self.is_mod or self in area.owners or self.char_id == -1) and not area.is_char_available( + self.char_id + ): self.check_char_taken(area) - + old_area = self.area self.set_area(area, target_pos) self.last_move_time = round(time.time() * 1000.0) @@ -1253,18 +1158,13 @@ def change_area(self, area, password=""): if c.following == self: if self.area.area_manager != c.area.area_manager: # The person we're following may be trying to sneak away from us. - c.unfollow( - silent=not allowed and ( - self.hidden or self.sneaking) - ) + c.unfollow(silent=not allowed and (self.hidden or self.sneaking)) continue # If they're still in the same hub, we're not hidden/sneaking or they're a mod, gm or cm # Attempt to transfer to their area try: c.change_area(area) - c.send_ooc( - f"Following [{self.id}] {self.showname} to [{area.id}] {area.name}." - ) + c.send_ooc(f"Following [{self.id}] {self.showname} to [{area.id}] {area.name}.") # Something obstructed us. except ClientError as ex: c.send_ooc(ex) @@ -1275,12 +1175,7 @@ def change_area(self, area, password=""): self.refresh_area_char_links(old_area) reason = "" - if ( - not self.area.dark - and not self.area.force_sneak - and not self.sneaking - and not self.hidden - ): + if not self.area.dark and not self.area.force_sneak and not self.sneaking and not self.hidden: if not old_area.dark and not old_area.force_sneak: if old_area.area_manager == self.area.area_manager: if self.area.area_manager.passing_msg is True: @@ -1386,9 +1281,7 @@ def change_area(self, area, password=""): ) if self.area.cannot_ic_interact(self): - self.send_ooc( - "This area is muted - you cannot talk in-character unless invited." - ) + self.send_ooc("This area is muted - you cannot talk in-character unless invited.") # CU Packet # CU#####% @@ -1433,7 +1326,7 @@ def clear_user_links(self): self.send_command("CU", "1", "2") def get_new_area_user_links(self): - """" + """ " Get the char_urls of the new area that the client changed to. And applying the changes. """ @@ -1449,8 +1342,6 @@ def get_new_area_user_links(self): for client in clients: self.add_user_link(client.char_name, client.char_url) - - def refresh_area_char_links(self, old_area): """ Clears all user links in the old area. @@ -1508,16 +1399,12 @@ def get_area_list(self, hidden=False, unlinked=False): if str(area.id) not in self.area.links: if not unlinked: continue - if ( - not hidden - and self.area.links[str(area.id)]["hidden"] is True - ): + if not hidden and self.area.links[str(area.id)]["hidden"] is True: continue if ( not hidden and len(self.area.links[str(area.id)]["evidence"]) > 0 - and self.hidden_in - not in self.area.links[str(area.id)]["evidence"] + and self.hidden_in not in self.area.links[str(area.id)]["evidence"] ): continue @@ -1542,7 +1429,7 @@ def send_area_list(self, full=False): for _, area in enumerate(area_list): if area.hidden: continue - msg += f'\n{self.get_area_info(area.id, highlight_self=True)}' + msg += f"\n{self.get_area_info(area.id, highlight_self=True)}" self.send_ooc(msg) def get_area_info(self, area_id, highlight_self=False, hub=None): @@ -1565,12 +1452,7 @@ def get_area_info(self, area_id, highlight_self=False, hub=None): owner = f"[CM(s): {area.get_owners()}]" hidden = "📦" if area.hidden else "" locked = "🔒" if area.locked else "" - pathlocked = ( - "🚧" - if str(area.id) in self.area.links - and self.area.links[str(area.id)]["locked"] - else "" - ) + pathlocked = "🚧" if str(area.id) in self.area.links and self.area.links[str(area.id)]["locked"] else "" passworded = "🔑" if area.password != "" else "" muted = "🔇" if area.muted else "" dark = "🌑" if area.dark else "" @@ -1584,17 +1466,19 @@ def get_area_info(self, area_id, highlight_self=False, hub=None): info += "❌" if not self.is_mod and self not in area.owners: if area.hide_clients or area.area_manager.hide_clients or area.dark: - users = '' + users = "" else: # We exclude hidden players here because we don't want them to count for the user count player_list = [c for c in area.clients if not c.hidden] - users = f' (users: {len(player_list)}) ' + users = f" (users: {len(player_list)}) " if area.hidden: return "" else: - users = f' (users: {len(area.clients)}) ' + users = f" (users: {len(area.clients)}) " - info += f"[{area.id}] {area.name}{users}{status}{owner}{hidden}{locked}{pathlocked}{passworded}{muted}{dark}" + info += ( + f"[{area.id}] {area.name}{users}{status}{owner}{hidden}{locked}{pathlocked}{passworded}{muted}{dark}" + ) return info def get_area_clients(self, area_id, mods=False, afk_check=False, show_links=False, hub=None): @@ -1628,19 +1512,23 @@ def get_area_clients(self, area_id, mods=False, afk_check=False, show_links=Fals # Afterwards, sort the client list based on their unique role or status sorted_clients = sorted( sorted_clients, - key=lambda x: 1 - if (x in area.afkers) - else 2 - if x.hidden - else 3 - if x.char_id == -1 - else 4 - if (x in area._owners) - else 5 - if (x in area.area_manager.owners) - else 6 - if x.is_mod - else 0, + key=lambda x: ( + 1 + if (x in area.afkers) + else ( + 2 + if x.hidden + else ( + 3 + if x.char_id == -1 + else ( + 4 + if (x in area._owners) + else 5 if (x in area.area_manager.owners) else 6 if x.is_mod else 0 + ) + ) + ) + ), ) for c in sorted_clients: info += "\r\n" @@ -1683,16 +1571,11 @@ def send_areas_clients(self, mods=False, afk_check=False, show_links=False): :param mods: if true, limit player list to mods :param afk_check: if true, limit player list to afks """ - if ( - not self.is_mod - and self not in self.area.area_manager.owners - and self.char_id != -1 - ): + if not self.is_mod and self not in self.area.area_manager.owners and self.char_id != -1: if self.blinded: raise ClientError("You are blinded!") if not self.area.area_manager.can_getareas: - raise ClientError( - "You cannot see players in all areas in this hub!") + raise ClientError("You cannot see players in all areas in this hub!") info = "🗺️ Clients in Areas 🗺️\n" cnt = 0 @@ -1706,7 +1589,7 @@ def send_areas_clients(self, mods=False, afk_check=False, show_links=False): # We exclude hidden players here because we don't want them to count for the user count client_list = [c for c in client_list if not c.hidden] - area_info = f'{self.get_area_info(i)}:' + area_info = f"{self.get_area_info(i)}:" if area_info == "": continue @@ -1717,10 +1600,7 @@ def send_areas_clients(self, mods=False, afk_check=False, show_links=False): if area_info == "": continue - if ( - len(client_list) > 0 - or len(area.owners) > 0 - ): + if len(client_list) > 0 or len(area.owners) > 0: cnt += len(client_list) info += f"{area_info}\n" if afk_check: @@ -1733,26 +1613,16 @@ def send_hubs_clients(self, mods=False, afk_check=False, show_links=False): """ Send information over OOC about all hubs. """ - if ( - not self.is_mod - and self not in self.area.area_manager.owners - and self.char_id != -1 - ): + if not self.is_mod and self not in self.area.area_manager.owners and self.char_id != -1: if self.blinded: raise ClientError("You are blinded!") if "can_gethubs" not in self.server.config or not self.server.config["can_gethubs"]: - raise ClientError( - "You are not permitted to use the /gethubs command in this server!" - ) + raise ClientError("You are not permitted to use the /gethubs command in this server!") cnt = 0 info = "\n🗺️ Clients in Hubs 🗺️\n" for hub in self.server.hub_manager.hubs: hub_info = "" - if ( - (not hub.can_getareas or hub.hide_clients) - and not self.is_mod - and self not in hub.owners - ): + if (not hub.can_getareas or hub.hide_clients) and not self.is_mod and self not in hub.owners: info += f"\n⛩[{hub.id}]{hub.name}⛩: ❌\n" else: for i in range(len(hub.areas)): @@ -1770,9 +1640,7 @@ def send_hubs_clients(self, mods=False, afk_check=False, show_links=False): continue try: - area_info += self.get_area_clients( - i, mods, afk_check, show_links, hub=hub - ) + area_info += self.get_area_clients(i, mods, afk_check, show_links, hub=hub) except ClientError: area_info = "" if area_info == "": @@ -1781,9 +1649,7 @@ def send_hubs_clients(self, mods=False, afk_check=False, show_links=False): if len(client_list) > 0 or len(area.owners) > 0: cnt += len(client_list) hub_info += f"{area_info}\n" - if not hub_info == "" and ( - hub.can_getareas or self.is_mod or self in hub.owners - ): + if not hub_info == "" and (hub.can_getareas or self.is_mod or self in hub.owners): if not self.is_mod and self not in hub.owners: hub_count = 0 for area in hub.areas: @@ -1810,11 +1676,11 @@ def send_area_info(self, area_id, mods=False, afk_check=False, show_links=False) if not self.is_mod and self not in self.area.owners: if self.blinded: raise ClientError("You are blinded!") - area_info = f'📍 Clients in {self.get_area_info(area_id)} 📍' + area_info = f"📍 Clients in {self.get_area_info(area_id)} 📍" try: area_info += self.get_area_clients(area_id, mods, afk_check, show_links) except ClientError as ex: - area_info += f'\n{ex}' + area_info += f"\n{ex}" info += area_info self.send_ooc(info) @@ -1874,9 +1740,7 @@ def char_select(self): def get_available_char_list(self): """Get a list of character IDs that the client can select.""" if len(self.charcurse) > 0: - avail_char_ids = set(range(len(self.area.area_manager.char_list))) and set( - self.charcurse - ) + avail_char_ids = set(range(len(self.area.area_manager.char_list))) and set(self.charcurse) else: avail_char_ids = set(range(len(self.area.area_manager.char_list))) - { x.char_id for x in self.area.clients @@ -1896,8 +1760,7 @@ def auth_mod(self, password): """ modpasses = self.server.config["modpass"] if isinstance(modpasses, dict): - matches = [k for k in modpasses if modpasses[k] - ["password"] == password] + matches = [k for k in modpasses if modpasses[k]["password"] == password] elif modpasses == password: matches = ["default"] else: @@ -1958,12 +1821,11 @@ def f_char_name_raw(self): if self.iniswap != "": return self.iniswap - if ( self.char_id is None - or self.char_id <= -1 - or self.char_id >= len(self.area.area_manager.char_list)): + if self.char_id is None or self.char_id <= -1 or self.char_id >= len(self.area.area_manager.char_list): return "" return self.area.area_manager.char_list[self.char_id] + @property def f_char_name(self): """Get the name of either the character that the client is using or the iniswap.""" @@ -1996,15 +1858,12 @@ def showname(self, value): @property def move_delay(self): """Get the character's movement delay.""" - return self.area.area_manager.get_character_data( - self.char_id, "move_delay", 0 - ) + return self.area.area_manager.get_character_data(self.char_id, "move_delay", 0) @move_delay.setter def move_delay(self, value): """Set the character's move delay in the character data.""" - self.area.area_manager.set_character_data( - self.char_id, "move_delay", value) + self.area.area_manager.set_character_data(self.char_id, "move_delay", value) @property def keys(self): @@ -2014,8 +1873,7 @@ def keys(self): @keys.setter def keys(self, value): """Set the character's keys in the character data.""" - self.area.area_manager.set_character_data( - self.char_id, "keys", value) + self.area.area_manager.set_character_data(self.char_id, "keys", value) @property def desc(self): @@ -2025,8 +1883,7 @@ def desc(self): @desc.setter def desc(self, value): """Set the character's description character data.""" - self.area.area_manager.set_character_data( - self.char_id, "desc", value) + self.area.area_manager.set_character_data(self.char_id, "desc", value) @property def hidden(self): @@ -2036,34 +1893,28 @@ def hidden(self): @property def inventory(self): """Get the character's inventory.""" - return self.area.area_manager.get_character_data( - self.char_id, "inventory", list() - ) + return self.area.area_manager.get_character_data(self.char_id, "inventory", list()) @inventory.setter def inventory(self, value): """Set the character's inventory.""" - self.area.area_manager.set_character_data( - self.char_id, "inventory", list(value)) + self.area.area_manager.set_character_data(self.char_id, "inventory", list(value)) @property def latest_area(self): """Get the character's latest occupied area ID.""" - return self.area.area_manager.get_character_data( - self.char_id, "latest_area", None - ) + return self.area.area_manager.get_character_data(self.char_id, "latest_area", None) @latest_area.setter def latest_area(self, value): """Set the character's latest occupied area ID.""" - self.area.area_manager.set_character_data( - self.char_id, "latest_area", int(value)) - + self.area.area_manager.set_character_data(self.char_id, "latest_area", int(value)) + def add_inventory_evidence(self, name, desc, image): inventory = self.area.area_manager.get_character_data(self.char_id, "inventory", list()) inventory.append([name, desc, image]) self.area.area_manager.set_character_data(self.char_id, "inventory", inventory) - + def remove_inventory_evidence(self, index): inventory = self.area.area_manager.get_character_data(self.char_id, "inventory", list()) inventory.pop(index) @@ -2080,9 +1931,7 @@ def hide(self, tog=True, target=None, hidden=False): continue if target.lower() == evi.name.lower() or target == str(i): if not self.area.evi_list.can_hide_in(evi): - raise ClientError( - "Targeted evidence cannot be hidden in." - ) + raise ClientError("Targeted evidence cannot be hidden in.") evidence = i break if evidence is not None: @@ -2091,9 +1940,7 @@ def hide(self, tog=True, target=None, hidden=False): c = evi.hiding_client c.hide(False) c.area.broadcast_area_list(c) - raise ClientError( - f"{c.showname} was already hiding in that evidence!" - ) + raise ClientError(f"{c.showname} was already hiding in that evidence!") self.hidden_in = evidence evi.hiding_client = self msg += f" inside the {evi.name}" @@ -2105,9 +1952,7 @@ def hide(self, tog=True, target=None, hidden=False): evi.hiding_client = None self.hidden_in = None if not hidden: - self.area.broadcast_ooc( - f"{self.showname} emerges from the {evi.name}!" - ) + self.area.broadcast_ooc(f"{self.showname} emerges from the {evi.name}!") # Impose all move delays as if we moved an area when unhiding so people have to be smart about it self.last_move_time = round(time.time() * 1000.0) @@ -2122,9 +1967,7 @@ def blind(self, tog=True): msg = "no longer" if tog: msg = "now" - self.send_ooc( - f"You are {msg} blinded from the area and seeing non-broadcasted IC messages." - ) + self.send_ooc(f"You are {msg} blinded from the area and seeing non-broadcasted IC messages.") self.send_command("LE", *self.area.get_evidence_list(self)) if not self.hidden and not self.sneaking: self.area.broadcast_player_list() @@ -2136,25 +1979,20 @@ def freeze(self, tog=True): msg = "no longer" if tog: msg = "now" - self.send_ooc( - f"You are {msg} frozen from moving between areas." - ) + self.send_ooc(f"You are {msg} frozen from moving between areas.") def sneak(self, tog=True): self.sneaking = tog msg = "no longer" if tog: msg = "now" - self.send_ooc( - f"You are {msg} sneaking (area transfer announcements will {msg} be hidden)." - ) + self.send_ooc(f"You are {msg} sneaking (area transfer announcements will {msg} be hidden).") def follow(self, target): try: self.change_area(target.area) self.following = target - self.send_ooc( - f"You are now following [{target.id}] {target.showname}.") + self.send_ooc(f"You are now following [{target.id}] {target.showname}.") except ValueError: raise except (AreaError, ClientError): @@ -2164,9 +2002,7 @@ def unfollow(self, silent=False): if self.following is not None: try: if not silent: - self.send_ooc( - f"You are no longer following [{self.following.id}] {self.following.showname}." - ) + self.send_ooc(f"You are no longer following [{self.following.id}] {self.following.showname}.") self.following = None except Exception: self.following = None @@ -2206,8 +2042,7 @@ def set_need_call_delay(self): """Begin the need cooldown.""" try: self.need_call_time = round( - time.time() * 1000.0 - + int(self.server.config["need_webhook"]["delay"]) * 1000.0 + time.time() * 1000.0 + int(self.server.config["need_webhook"]["delay"]) * 1000.0 ) except Exception: self.need_call_time = round(time.time() * 1000 + 60000) @@ -2236,12 +2071,12 @@ def shake_message(self, message): def rainbow_message(self, message): """Turn the message into rainbows (base color assumed to be blue)""" # red orange yellow green cyan blue magenta - color_array = ['~', '|', 'º', '`', '√', '', '№'] - constructed_message = '' + color_array = ["~", "|", "º", "`", "√", "", "№"] + constructed_message = "" # pseudo-randomize the rainbows based on msg length index = len(message) % len(color_array) for symbol in message: - symbol = f'{color_array[index]}{symbol}{color_array[index]}' + symbol = f"{color_array[index]}{symbol}{color_array[index]}" constructed_message += symbol index = (index + 1) % len(color_array) return constructed_message @@ -2285,8 +2120,7 @@ def new_client(self, transport): peername = transport.get_extra_info("peername")[0] - c = self.Client(self.server, transport, user_id, - database.ipid(peername)) + c = self.Client(self.server, transport, user_id, database.ipid(peername)) self.clients.add(c) temp_ipid = c.ipid for client in self.server.client_manager.clients: @@ -2339,10 +2173,7 @@ def remove_client(self, client): "FA", *[ "🌐 Hubs 🌐\n Double-Click me to see Areas\n _______", - *[ - f"[{hub.id}] {hub.name} (users: {hub.count})" - for hub in self.server.hub_manager.hubs - ], + *[f"[{hub.id}] {hub.name} (users: {hub.count})" for hub in self.server.hub_manager.hubs], ], ) @@ -2385,7 +2216,7 @@ def get_targets(self, client, key, value, local=False, single=False, all_hub=Fal targets.append(client) elif key == TargetType.ID: if client.id == value: - targets.append(client) + targets.append(client) elif key == TargetType.IPID: if client.ipid == value: targets.append(client) @@ -2413,8 +2244,7 @@ def get_ooc_muted_clients(self): def toggle_afk(self, client): if client in client.area.afkers: if not client.hidden and not client.sneaking: - client.area.broadcast_ooc( - "{} is no longer AFK.".format(client.showname)) + client.area.broadcast_ooc("{} is no longer AFK.".format(client.showname)) client.send_ooc( "You are no longer AFK. Welcome back!" ) # Making the server a bit friendly wouldn't hurt, right? @@ -2443,7 +2273,7 @@ def get_multiclients(self, ipid=-1, hdid=""): def get_mods(self): return [c for c in self.clients if c.is_mod] - + class BattleChar: def __init__(self, client, fighter_name, fighter): self.fighter = fighter_name diff --git a/server/commands/__init__.py b/server/commands/__init__.py index 5a3ba580..6978cf11 100644 --- a/server/commands/__init__.py +++ b/server/commands/__init__.py @@ -7,9 +7,7 @@ def call(client, cmd, arg): if cmd in client.server.command_aliases: called_function = f"ooc_cmd_{client.server.command_aliases[cmd]}" if not hasattr(me, called_function): - client.send_ooc( - f"Invalid command: {cmd}. Use /help to find up-to-date commands." - ) + client.send_ooc(f"Invalid command: {cmd}. Use /help to find up-to-date commands.") return getattr(me, called_function)(client, arg) @@ -68,11 +66,7 @@ def list_commands(submodule=""): import inspect cmds = "" - modules = [ - a - for a in submodules() - if submodule == "" or a.__name__.split(".")[-1] == submodule - ] + modules = [a for a in submodules() if submodule == "" or a.__name__.split(".")[-1] == submodule] if len(modules) == 0: raise AttributeError for module in modules: @@ -85,7 +79,7 @@ def list_commands(submodule=""): doc = doc[: doc.find(".") + 1] prefix = "ooc_cmd_" if func.startswith(prefix): - func = func[len(prefix):] + func = func[len(prefix) :] cmds += f"{func} - {doc}\n" return cmds diff --git a/server/commands/admin.py b/server/commands/admin.py index e9b12148..a8c8fa6e 100644 --- a/server/commands/admin.py +++ b/server/commands/admin.py @@ -54,8 +54,7 @@ def ooc_cmd_help(client, arg): import inspect if arg == "": - msg = inspect.cleandoc( - """ + msg = inspect.cleandoc(""" Welcome to tsuserver3! You can use /help on any known command to get up-to-date help on it. You may also use /help to see available commands for that category. @@ -66,8 +65,7 @@ def ooc_cmd_help(client, arg): https://github.com/Crystalwarrior/KFO-Server/blob/master/README.md Available Categories: - """ - ) + """) msg += "\n" msg += list_submodules() client.send_ooc(msg) @@ -83,9 +81,7 @@ def ooc_cmd_help(client, arg): msg += list_commands(arg) client.send_ooc(msg) except AttributeError: - client.send_ooc( - f"No such command or submodule ({arg}) has been found in the help docs." - ) + client.send_ooc(f"No such command or submodule ({arg}) has been found in the help docs.") @mod_only() @@ -98,8 +94,7 @@ def ooc_cmd_kick(client, arg): - "**" kicks everyone in the server. """ if len(arg) == 0: - raise ArgumentError( - "You must specify a target. Use /kick [reason]") + raise ArgumentError("You must specify a target. Use /kick [reason]") elif arg[0] == "*": targets = [c for c in client.area.clients if c != client] elif arg[0] == "**": @@ -114,15 +109,12 @@ def ooc_cmd_kick(client, arg): ipid = int(raw_ipid) except Exception: raise ClientError(f"{raw_ipid} does not look like a valid IPID.") - targets = client.server.client_manager.get_targets( - client, TargetType.IPID, ipid, False - ) + targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if targets: reason = " ".join(args[1:]) for c in targets: - database.log_misc("kick", client, target=c, - data={"reason": reason}) + database.log_misc("kick", client, target=c, data={"reason": reason}) client.send_ooc(f"{c.showname} was kicked.") c.send_command("KK", reason) c.disconnect() @@ -175,9 +167,7 @@ def kickban(client, arg, ban_hdid): raise ArgumentError("Invalid ban duration.") unban_date = arrow.get().shift(seconds=duration).datetime else: - raise ArgumentError( - f"Ambiguous input: {arg}\nPlease wrap your arguments " "in quotes." - ) + raise ArgumentError(f"Ambiguous input: {arg}\nPlease wrap your arguments " "in quotes.") try: raw_ipid = args[0] @@ -197,24 +187,19 @@ def kickban(client, arg, ban_hdid): char = None hdid = None if ipid is not None: - targets = client.server.client_manager.get_targets( - client, TargetType.IPID, ipid, False - ) + targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if targets: for c in targets: if ban_hdid: - database.ban(c.hdid, reason, - ban_type="hdid", ban_id=ban_id) + database.ban(c.hdid, reason, ban_type="hdid", ban_id=ban_id) hdid = c.hdid c.send_command("KB", reason) c.disconnect() char = c.char_name - database.log_misc("ban", client, target=c, - data={"reason": reason}) + database.log_misc("ban", client, target=c, data={"reason": reason}) client.send_ooc(f"{len(targets)} clients were kicked.") client.send_ooc(f"{ipid} was banned. Ban ID: {ban_id}") - client.server.webhooks.ban( - ipid, ban_id, reason, client, hdid, char, unban_date) + client.server.webhooks.ban(ipid, ban_id, reason, client, hdid, char, unban_date) @mod_only() @@ -224,8 +209,7 @@ def ooc_cmd_unban(client, arg): Usage: /unban """ if len(arg) == 0: - raise ArgumentError( - "You must specify a target. Use /unban ") + raise ArgumentError("You must specify a target. Use /unban ") args = list(arg.split(" ")) client.send_ooc(f"Attempting to lift {len(args)} ban(s)...") for ban_id in args: @@ -250,9 +234,7 @@ def ooc_cmd_mute(client, arg): for raw_ipid in args: if raw_ipid.isdigit(): ipid = int(raw_ipid) - clients = client.server.client_manager.get_targets( - client, TargetType.IPID, ipid, False - ) + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if clients: msg = "Muted the IPID " + str(ipid) + "'s following clients:" for c in clients: @@ -263,9 +245,7 @@ def ooc_cmd_mute(client, arg): msg += "." client.send_ooc(msg) else: - client.send_ooc( - "No targets found. Use /mute ... for mute." - ) + client.send_ooc("No targets found. Use /mute ... for mute.") else: client.send_ooc(f"{raw_ipid} does not look like a valid IPID.") @@ -283,9 +263,7 @@ def ooc_cmd_unmute(client, arg): for raw_ipid in args: if raw_ipid.isdigit(): ipid = int(raw_ipid) - clients = client.server.client_manager.get_targets( - client, TargetType.IPID, ipid, False - ) + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if clients: msg = f"Unmuted the IPID ${str(ipid)}'s following clients:" for c in clients: @@ -296,9 +274,7 @@ def ooc_cmd_unmute(client, arg): msg += "." client.send_ooc(msg) else: - client.send_ooc( - "No targets found. Use /unmute ... for unmute." - ) + client.send_ooc("No targets found. Use /unmute ... for unmute.") else: client.send_ooc(f"{raw_ipid} does not look like a valid IPID.") @@ -382,11 +358,8 @@ def ooc_cmd_ooc_mute(client, arg): Usage: /ooc_mute """ if len(arg) == 0: - raise ArgumentError( - "You must specify a target. Use /ooc_mute .") - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, arg, False - ) + raise ArgumentError("You must specify a target. Use /ooc_mute .") + targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, arg, False) if not targets: raise ArgumentError("Targets not found. Use /ooc_mute .") for target in targets: @@ -402,8 +375,7 @@ def ooc_cmd_ooc_unmute(client, arg): Usage: /ooc_unmute """ if len(arg) == 0: - raise ArgumentError( - "You must specify a target. Use /ooc_unmute .") + raise ArgumentError("You must specify a target. Use /ooc_unmute .") targets = client.server.client_manager.get_ooc_muted_clients() if not targets: raise ArgumentError("Targets not found. Use /ooc_unmute .") @@ -422,10 +394,7 @@ def ooc_cmd_bans(client, _arg): msg = "Last 5 bans:\n" for ban in database.recent_bans(): time = arrow.get(ban.ban_date).humanize() - msg += ( - f"{time}: {ban.banned_by_name} ({ban.banned_by}) issued ban " - f"{ban.ban_id} ('{ban.reason}')\n" - ) + msg += f"{time}: {ban.banned_by_name} ({ban.banned_by}) issued ban " f"{ban.ban_id} ('{ban.reason}')\n" client.send_ooc(msg) @@ -452,8 +421,7 @@ def ooc_cmd_baninfo(client, arg): client.send_ooc("No ban found for this ID.") else: msg = f"Ban ID: {ban.ban_id}\n" - msg += "Affected IPIDs: " + \ - ", ".join([str(ipid) for ipid in ban.ipids]) + "\n" + msg += "Affected IPIDs: " + ", ".join([str(ipid) for ipid in ban.ipids]) + "\n" msg += "Affected HDIDs: " + ", ".join(ban.hdids) + "\n" msg += f'Reason: "{ban.reason}"\n' msg += f"Banned by: {ban.banned_by_name} ({ban.banned_by})\n" @@ -523,8 +491,7 @@ def ooc_cmd_restart(client, arg): if arg != client.server.config["restartpass"]: raise ArgumentError("no") print(f"!!!{client.name} called /restart!!!") - client.server.send_all_cmd_pred( - "CT", "WARNING", "Restarting the server...") + client.server.send_all_cmd_pred("CT", "WARNING", "Restarting the server...") asyncio.get_running_loop().stop() diff --git a/server/commands/area_access.py b/server/commands/area_access.py index 8c0c4187..b2f7da51 100644 --- a/server/commands/area_access.py +++ b/server/commands/area_access.py @@ -44,25 +44,16 @@ def ooc_cmd_area_lock(client, arg): if not client.is_mod and client not in area.owners: if str(area.id) not in client.keys: if area.locking_allowed and area != client.area: - client.send_ooc( - "You can only lock that area from within!") + client.send_ooc("You can only lock that area from within!") continue if not area.locking_allowed: - client.send_ooc( - f"You don't have the keys to {area.name}.") + client.send_ooc(f"You don't have the keys to {area.name}.") continue if not client.can_access_area(area): - client.send_ooc( - f"You have the keys to {area.name} but it is not accessible from your area." - ) + client.send_ooc(f"You have the keys to {area.name} but it is not accessible from your area.") continue - if ( - str(area.id) in client.area.links - and client.area.links[str(area.id)]["locked"] - ): - client.send_ooc( - f"You have the keys to {area.name} but the path is locked." - ) + if str(area.id) in client.area.links and client.area.links[str(area.id)]["locked"]: + client.send_ooc(f"You have the keys to {area.name} but the path is locked.") continue if area.locked: client.send_ooc(f"Area {area.name} is already locked.") @@ -94,8 +85,7 @@ def ooc_cmd_area_mute(client, arg): continue if area.muted: - client.send_ooc( - f"Area [{area.id}] {area.name} is already muted.") + client.send_ooc(f"Area [{area.id}] {area.name} is already muted.") continue area.mute() area.broadcast_ooc("This area is now muted.") @@ -125,8 +115,7 @@ def ooc_cmd_area_unmute(client, arg): continue if not area.muted: - client.send_ooc( - f"Area [{area.id}] {area.name} is already unmuted.") + client.send_ooc(f"Area [{area.id}] {area.name} is already unmuted.") continue area.unmute() area.broadcast_ooc("This area is no longer muted.") @@ -153,25 +142,16 @@ def ooc_cmd_area_unlock(client, arg): if not client.is_mod and client not in area.owners: if str(area.id) not in client.keys: if area.locking_allowed and area != client.area: - client.send_ooc( - "You can only unlock that area from within!") + client.send_ooc("You can only unlock that area from within!") continue if not area.locking_allowed: - client.send_ooc( - "You don't have the keys to {area.name}.") + client.send_ooc("You don't have the keys to {area.name}.") continue if not client.can_access_area(area): - client.send_ooc( - f"You have the keys to {area.name} but it is not accessible from your area." - ) + client.send_ooc(f"You have the keys to {area.name} but it is not accessible from your area.") continue - if ( - str(area.id) in client.area.links - and client.area.links[str(area.id)]["locked"] - ): - client.send_ooc( - f"You have the keys to {area.name} but the path is locked." - ) + if str(area.id) in client.area.links and client.area.links[str(area.id)]["locked"]: + client.send_ooc(f"You have the keys to {area.name} but the path is locked.") continue if not area.locked: client.send_ooc(f"Area {area.name} is already unlocked.") @@ -263,9 +243,7 @@ def ooc_cmd_link(client, arg): area.link(client.area.id) links.append(target_id) links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} has been linked with {links} (two-way)." - ) + client.send_ooc(f"Area {client.area.name} has been linked with {links} (two-way).") client.area.broadcast_area_list() area.broadcast_area_list() except ValueError: @@ -305,9 +283,7 @@ def ooc_cmd_unlink(client, arg): except Exception: continue links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} has been unlinked with {links} (two-way)." - ) + client.send_ooc(f"Area {client.area.name} has been unlinked with {links} (two-way).") client.area.broadcast_area_list() area.broadcast_area_list() except ValueError: @@ -383,9 +359,7 @@ def ooc_cmd_onelink(client, arg): client.area.link(target_id) links.append(target_id) links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} has been linked with {links} (one-way)." - ) + client.send_ooc(f"Area {client.area.name} has been linked with {links} (one-way).") client.area.broadcast_area_list() except ValueError: raise ArgumentError("Area ID must be a number or abbreviation.") @@ -401,14 +375,12 @@ def ooc_cmd_oneunlink(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /oneunlink ") + raise ArgumentError("Invalid number of arguments. Use /oneunlink ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) @@ -418,9 +390,7 @@ def ooc_cmd_oneunlink(client, arg): except Exception: continue links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} has been unlinked with {links} (one-way)." - ) + client.send_ooc(f"Area {client.area.name} has been unlinked with {links} (one-way).") client.area.broadcast_area_list() except ValueError: raise ArgumentError("Area ID must be a number or abbreviation.") @@ -435,40 +405,31 @@ def ooc_cmd_link_lock(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_lock ") + raise ArgumentError("Invalid number of arguments. Use /link_lock ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) if not client.is_mod and client not in client.area.owners: if f"{client.area.id}-{target_id}" not in client.keys: - client.send_ooc( - f"You don't have the keys to the link {client.area.id}-{target_id}." - ) + client.send_ooc(f"You don't have the keys to the link {client.area.id}-{target_id}.") continue - target_area = client.area.area_manager.get_area_by_id( - target_id) + target_area = client.area.area_manager.get_area_by_id(target_id) if ( - f"{target_id}-{client.area.id}" in client.keys - and str(client.area.id) in target_area.links + f"{target_id}-{client.area.id}" in client.keys and str(client.area.id) in target_area.links ): # Treat it as a single door/path if we have the keys both ways target_area.links[str(client.area.id)]["locked"] = True - client.send_ooc( - f"Locked {client.area.id}-{target_id} both ways.") + client.send_ooc(f"Locked {client.area.id}-{target_id} both ways.") client.area.links[str(target_id)]["locked"] = True links.append(target_id) if len(links) > 0: links = ", ".join(str(link) for link in links) client.send_ooc(f"Area {client.area.name} links {links} locked.") except (ValueError, KeyError): - raise ArgumentError( - "Area ID must be a number or abbreviation and the link must exist." - ) + raise ArgumentError("Area ID must be a number or abbreviation and the link must exist.") except (AreaError, ClientError): raise @@ -480,40 +441,31 @@ def ooc_cmd_link_unlock(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_unlock ") + raise ArgumentError("Invalid number of arguments. Use /link_unlock ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) if not client.is_mod and client not in client.area.owners: if f"{client.area.id}-{target_id}" not in client.keys: - client.send_ooc( - f"You don't have the keys to the link {client.area.id}-{target_id}." - ) + client.send_ooc(f"You don't have the keys to the link {client.area.id}-{target_id}.") continue - target_area = client.area.area_manager.get_area_by_id( - target_id) + target_area = client.area.area_manager.get_area_by_id(target_id) if ( - f"{target_id}-{client.area.id}" in client.keys - and str(client.area.id) in target_area.links + f"{target_id}-{client.area.id}" in client.keys and str(client.area.id) in target_area.links ): # Treat it as a single door/path if we have the keys both ways target_area.links[str(client.area.id)]["locked"] = False - client.send_ooc( - f"Unlocked {client.area.id}-{target_id} both ways.") + client.send_ooc(f"Unlocked {client.area.id}-{target_id} both ways.") client.area.links[str(target_id)]["locked"] = False links.append(target_id) if len(links) > 0: links = ", ".join(str(link) for link in links) client.send_ooc(f"Area {client.area.name} links {links} unlocked.") except (ValueError, KeyError): - raise ArgumentError( - "Area ID must be a number or abbreviation and the link must exist." - ) + raise ArgumentError("Area ID must be a number or abbreviation and the link must exist.") except (AreaError, ClientError): raise @@ -526,14 +478,12 @@ def ooc_cmd_link_hide(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_hide ") + raise ArgumentError("Invalid number of arguments. Use /link_hide ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) @@ -556,14 +506,12 @@ def ooc_cmd_link_unhide(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_unhide ") + raise ArgumentError("Invalid number of arguments. Use /link_unhide ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) @@ -586,20 +534,16 @@ def ooc_cmd_link_pos(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_unhide ") + raise ArgumentError("Invalid number of arguments. Use /link_unhide ") try: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - args[0]).id + target_id = client.area.area_manager.get_area_by_abbreviation(args[0]).id except Exception: target_id = int(args[0]) - pos = ' '.join(args[1:]) + pos = " ".join(args[1:]) client.area.links[str(target_id)]["target_pos"] = pos - client.send_ooc( - f'Area {client.area.name} link {target_id}\'s target pos set to "{pos}".' - ) + client.send_ooc(f'Area {client.area.name} link {target_id}\'s target pos set to "{pos}".') except (ValueError, KeyError): raise ArgumentError("Area ID must be a number or abbreviation.") except (AreaError, ClientError): @@ -614,14 +558,12 @@ def ooc_cmd_link_peekable(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_peekable ") + raise ArgumentError("Invalid number of arguments. Use /link_peekable ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) @@ -629,8 +571,7 @@ def ooc_cmd_link_peekable(client, arg): links.append(target_id) if len(links) > 0: links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} links {links} are now peekable.") + client.send_ooc(f"Area {client.area.name} links {links} are now peekable.") except (ValueError, KeyError): raise ArgumentError("Area ID must be a number or abbreviation.") except (AreaError, ClientError): @@ -645,14 +586,12 @@ def ooc_cmd_link_unpeekable(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_unpeekable ") + raise ArgumentError("Invalid number of arguments. Use /link_unpeekable ") try: links = [] for aid in args: try: - target_id = client.area.area_manager.get_area_by_abbreviation( - aid).id + target_id = client.area.area_manager.get_area_by_abbreviation(aid).id except Exception: target_id = int(aid) @@ -660,9 +599,7 @@ def ooc_cmd_link_unpeekable(client, arg): links.append(target_id) if len(links) > 0: links = ", ".join(str(link) for link in links) - client.send_ooc( - f"Area {client.area.name} links {links} are no longer peekable." - ) + client.send_ooc(f"Area {client.area.name} links {links} are no longer peekable.") except (ValueError, KeyError): raise ArgumentError("Area ID must be a number or abbreviation.") except (AreaError, ClientError): @@ -678,9 +615,7 @@ def ooc_cmd_link_evidence(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /link_evidence [evi_id(s)]" - ) + raise ArgumentError("Invalid number of arguments. Use /link_evidence [evi_id(s)]") link = None evidences = [] try: @@ -688,9 +623,7 @@ def ooc_cmd_link_evidence(client, arg): if len(args) > 1: for evi_id in args[1:]: evi_id = int(evi_id) - 1 - client.area.evi_list.evidences[ - evi_id - ] # Test if we can access target evidence + client.area.evi_list.evidences[evi_id] # Test if we can access target evidence evidences.append(evi_id) except IndexError: raise ArgumentError("Evidence not found.") @@ -704,13 +637,9 @@ def ooc_cmd_link_evidence(client, arg): if len(link["evidence"]) > 0: evi_list = ", ".join(str(evi + 1) for evi in link["evidence"]) - client.send_ooc( - f"Area {client.area.name} link {args[0]} associated evidence IDs: {evi_list}." - ) + client.send_ooc(f"Area {client.area.name} link {args[0]} associated evidence IDs: {evi_list}.") else: - client.send_ooc( - f"Area {client.area.name} link {args[0]} has no associated evidence." - ) + client.send_ooc(f"Area {client.area.name} link {args[0]} has no associated evidence.") @mod_only(area_owners=True) @@ -722,9 +651,7 @@ def ooc_cmd_unlink_evidence(client, arg): """ args = arg.split() if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /unlink_evidence [evi_id(s)]" - ) + raise ArgumentError("Invalid number of arguments. Use /unlink_evidence [evi_id(s)]") link = None evidences = [] try: @@ -741,14 +668,10 @@ def ooc_cmd_unlink_evidence(client, arg): if len(evidences) > 0: link["evidence"] = link["evidence"] - evidences evi_list = ", ".join(str(evi + 1) for evi in evidences) - client.send_ooc( - f"Area {client.area.name} link {args[0]} is now unlinked from evidence IDs: {evi_list}." - ) + client.send_ooc(f"Area {client.area.name} link {args[0]} is now unlinked from evidence IDs: {evi_list}.") else: link["evidence"] = [] - client.send_ooc( - f"Area {client.area.name} link {args[0]} associated evidences cleared." - ) + client.send_ooc(f"Area {client.area.name} link {args[0]} associated evidences cleared.") def ooc_cmd_pw(client, arg): @@ -762,9 +685,7 @@ def ooc_cmd_pw(client, arg): password = "" if arg == "": if not client.is_mod and client not in client.area.owners: - raise ArgumentError( - "You are not allowed to see this area's password. Use /pw [password]" - ) + raise ArgumentError("You are not allowed to see this area's password. Use /pw [password]") aid = client.area.id else: args = arg.split() @@ -779,17 +700,11 @@ def ooc_cmd_pw(client, arg): if password == "": if client.is_mod or client in client.area.owners: if link is not None and link["password"] != "": - client.send_ooc( - f'Link {client.area.id}-{area.id} password is: {link["password"]}' - ) + client.send_ooc(f'Link {client.area.id}-{area.id} password is: {link["password"]}') else: - client.send_ooc( - f"Area [{area.id}] {area.name} password is: {area.password}" - ) + client.send_ooc(f"Area [{area.id}] {area.name} password is: {area.password}") else: - raise ClientError( - "You must provide a password. Use /pw [password]" - ) + raise ClientError("You must provide a password. Use /pw [password]") else: client.change_area(area, password=password) except ValueError: @@ -808,8 +723,7 @@ def ooc_cmd_setpw(client, arg): """ args = arg.split() if len(args) == 0: - raise ArgumentError( - "Invalid number of arguments. Use /setpw [password]") + raise ArgumentError("Invalid number of arguments. Use /setpw [password]") try: password = "" @@ -821,8 +735,7 @@ def ooc_cmd_setpw(client, arg): link = client.area.links[num] area = client.area.area_manager.get_area_by_id(int(num)) else: - raise ArgumentError( - "Targeted link does not exist in current area.") + raise ArgumentError("Targeted link does not exist in current area.") else: area = client.area.area_manager.get_area_by_id(int(args[0])) if len(args) > 1: @@ -831,16 +744,11 @@ def ooc_cmd_setpw(client, arg): raise ClientError("You do not own that area!") if link is not None: link["password"] = password - client.send_ooc( - f"Link {client.area.id}-{area.id} password set to: {password}" - ) + client.send_ooc(f"Link {client.area.id}-{area.id} password set to: {password}") else: area.password = password - client.send_ooc( - f"Area [{area.id}] {area.name} password set to: {password}") + client.send_ooc(f"Area [{area.id}] {area.name} password set to: {password}") except ValueError: - raise ArgumentError( - "Area ID must be a number, or a link ID must start with ! e.g. 5 vs !5." - ) + raise ArgumentError("Area ID must be a number, or a link ID must start with ! e.g. 5 vs !5.") except (AreaError, ClientError): raise diff --git a/server/commands/areas.py b/server/commands/areas.py index 6741545a..93da9d43 100644 --- a/server/commands/areas.py +++ b/server/commands/areas.py @@ -39,14 +39,14 @@ "ooc_cmd_clear_area_broadcast", ] + def ooc_cmd_overlay(client, arg): """ Set the overlay of an area. Usage: /overlay """ if len(arg) == 0: - client.send_ooc( - f"Current overlay is {client.area.overlay}. Use /overlay_clear to clear it.") + client.send_ooc(f"Current overlay is {client.area.overlay}. Use /overlay_clear to clear it.") return if client not in client.area.owners and not client.is_mod and client.area.overlay_lock: raise AreaError("This area's overlay system is locked!") @@ -54,11 +54,7 @@ def ooc_cmd_overlay(client, arg): raise AreaError("This area's background is locked!") if client.area.cannot_ic_interact(client): raise AreaError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") @@ -66,10 +62,10 @@ def ooc_cmd_overlay(client, arg): client.area.change_background(client.area._background, overlay=arg) except AreaError: raise - client.area.broadcast_ooc( - f"{client.showname} changed the overlay to {arg}.") + client.area.broadcast_ooc(f"{client.showname} changed the overlay to {arg}.") database.log_area("overlay", client, client.area, message=arg) + def ooc_cmd_overlay_clear(client, arg): """ Clear the overlay of an area. @@ -81,11 +77,7 @@ def ooc_cmd_overlay_clear(client, arg): raise AreaError("This area's background is locked!") if client.area.cannot_ic_interact(client): raise AreaError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") @@ -93,10 +85,10 @@ def ooc_cmd_overlay_clear(client, arg): client.area.change_background(client.area._background, overlay="") except AreaError: raise - client.area.broadcast_ooc( - f"{client.showname} cleared the overlay.") + client.area.broadcast_ooc(f"{client.showname} cleared the overlay.") database.log_area("overlay_clear", client, client.area) + def ooc_cmd_bg(client, arg): """ Set the background of an area. @@ -110,18 +102,13 @@ def ooc_cmd_bg(client, arg): suffix = "" if client.area.background_suffix: suffix = client.area.background_suffix - client.send_ooc( - f"Current background is '{client.area._background}' + '{suffix}'.{pos_lock}") + client.send_ooc(f"Current background is '{client.area._background}' + '{suffix}'.{pos_lock}") return if client not in client.area.owners and not client.is_mod and client.area.bg_lock: raise AreaError("This area's background is locked!") if client.area.cannot_ic_interact(client): raise AreaError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") if client.area.dark and not client.is_mod and client not in client.area.owners: raise ClientError("You must be authorized to do that.") @@ -129,8 +116,7 @@ def ooc_cmd_bg(client, arg): client.area.change_background(arg) except AreaError: raise - client.area.broadcast_ooc( - f"{client.showname} changed the background to {arg}.") + client.area.broadcast_ooc(f"{client.showname} changed the background to {arg}.") database.log_area("bg", client, client.area, message=arg) @@ -146,8 +132,7 @@ def ooc_cmd_bg_suffix(client, arg): client.area.change_background_suffix(arg) except AreaError: raise - client.send_ooc( - f"You changed the background suffix to {arg}.") + client.send_ooc(f"You changed the background suffix to {arg}.") database.log_area("bg", client, client.area, message=arg) @@ -180,28 +165,15 @@ def ooc_cmd_status(client, arg): if len(arg) == 0: client.send_ooc(f"Current status: {client.area.status}") else: - if ( - not client.area.can_change_status - and not client.is_mod - and client not in client.area.owners - ): - raise AreaError( - "This area's status cannot be changed by anyone who's not a CM or mod!" - ) + if not client.area.can_change_status and not client.is_mod and client not in client.area.owners: + raise AreaError("This area's status cannot be changed by anyone who's not a CM or mod!") if client.area.cannot_ic_interact(client): raise AreaError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") try: client.area.change_status(arg) - client.area.broadcast_ooc( - "{} changed status to {}.".format( - client.showname, client.area.status) - ) + client.area.broadcast_ooc("{} changed status to {}.".format(client.showname, client.area.status)) database.log_area("status", client, client.area, message=arg) except AreaError: raise @@ -221,12 +193,7 @@ def ooc_cmd_area(client, arg): a = arg.split(" ")[0] aid = a.strip("[]") if ( - ( - a.startswith("[") - and a.endswith("]") - and aid.isdigit() - and area.id == int(aid) - ) + (a.startswith("[") and a.endswith("]") and aid.isdigit() and area.id == int(aid)) or area.name.lower() == arg.lower() or area.abbreviation == arg or (arg.isdigit() and area.id == int(arg)) @@ -235,8 +202,7 @@ def ooc_cmd_area(client, arg): return raise AreaError("Targeted area not found!") except ValueError: - raise ArgumentError( - "Area ID must be a name, abbreviation or a number.") + raise ArgumentError("Area ID must be a name, abbreviation or a number.") except (AreaError, ClientError): raise @@ -251,9 +217,7 @@ def ooc_cmd_area_visible(client, arg): client.available_areas_only = not client.available_areas_only toggle = "enabled" if client.available_areas_only else "disabled" client.area.broadcast_area_list(client) - client.send_ooc( - f"You have {toggle} only displaying player-visible areas." - ) + client.send_ooc(f"You have {toggle} only displaying player-visible areas.") def ooc_cmd_autogetarea(client, arg): @@ -263,9 +227,7 @@ def ooc_cmd_autogetarea(client, arg): """ client.autogetarea = not client.autogetarea toggle = "enabled" if client.autogetarea else "disabled" - client.send_ooc( - f"You have {toggle} automatic /getarea." - ) + client.send_ooc(f"You have {toggle} automatic /getarea.") def ooc_cmd_getarea(client, arg): @@ -279,8 +241,7 @@ def ooc_cmd_getarea(client, arg): if area.id == client.area.id or (client.is_mod or client in area.owners): aid = int(arg) else: - raise ClientError( - "Can't see that area - insufficient permissions!") + raise ClientError("Can't see that area - insufficient permissions!") client.send_area_info(aid) client.area.broadcast_player_list_to_target(client) @@ -292,6 +253,7 @@ def ooc_cmd_getareas(client, arg): """ client.send_areas_clients() + def ooc_cmd_gethubs(client, arg): """ Show information about all hubs. @@ -299,6 +261,7 @@ def ooc_cmd_gethubs(client, arg): """ client.send_hubs_clients() + def ooc_cmd_getlink(client, arg): """ Show information about the current area, or target area id with sufficient permissions. @@ -311,8 +274,7 @@ def ooc_cmd_getlink(client, arg): if area.id == client.area.id or (client.is_mod or client in area.owners): aid = int(arg) else: - raise ClientError( - "Can't see that area - insufficient permissions!") + raise ClientError("Can't see that area - insufficient permissions!") client.send_area_info(aid, show_links=True) @@ -324,6 +286,7 @@ def ooc_cmd_getlinks(client, arg): """ client.send_areas_clients(show_links=True) + def ooc_cmd_getafk(client, arg): """ Show currently AFK-ing players in the current area or in all areas. @@ -348,11 +311,7 @@ def ooc_cmd_invite(client, arg): if not arg: msg = "Current invite list:\n" msg += "\n".join( - [ - f"[{c.id}] {c.showname}" - for c in client.server.client_manager.clients - if c.id in client.area.invite_list - ] + [f"[{c.id}] {c.showname}" for c in client.server.client_manager.clients if c.id in client.area.invite_list] ) msg += "\nUse /invite to invite someone." client.send_ooc(msg) @@ -362,15 +321,9 @@ def ooc_cmd_invite(client, arg): try: if args[0] == "*": - targets = [ - c - for c in client.area.clients - if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] else: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) except ValueError: raise ArgumentError("Area ID must be a number or *.") @@ -378,8 +331,7 @@ def ooc_cmd_invite(client, arg): for c in targets: client.area.invite_list.add(c.id) client.send_ooc(f"{c.showname} is invited to your area.") - c.send_ooc( - f"You were invited and given access to {client.area.name}.") + c.send_ooc(f"You were invited and given access to {client.area.name}.") database.log_area("invite", client, client.area, target=c) except Exception: raise ClientError("You must specify a target. Use /invite ") @@ -397,25 +349,16 @@ def ooc_cmd_uninvite(client, arg): args = arg.split(" ") try: if args[0] == "*": - targets = [ - c - for c in client.area.clients - if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] else: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) except ValueError: raise ArgumentError("Area ID must be a number or *.") if targets: try: for c in targets: - client.send_ooc( - "You have removed {} from the whitelist.".format( - c.showname) - ) + client.send_ooc("You have removed {} from the whitelist.".format(c.showname)) c.send_ooc("You were removed from the area whitelist.") database.log_area("uninvite", client, client.area, target=c) client.area.invite_list.discard(c.id) @@ -440,96 +383,57 @@ def ooc_cmd_area_kick(client, arg): Usage: /area_kick [destination] [target_pos] """ if not arg: - raise ClientError( - "You must specify a target. Use /area_kick [destination] [target_pos]" - ) + raise ClientError("You must specify a target. Use /area_kick [destination] [target_pos]") args = shlex.split(arg) # Kick everyone but AFKers if args[0] == "afk": - targets = client.server.client_manager.get_targets( - client, TargetType.AFK, args[0], False - ) + targets = client.server.client_manager.get_targets(client, TargetType.AFK, args[0], False) # Kick everyone but owners elif args[0] == "*": - targets = [ - c - for c in client.area.clients - if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] # Kick everyone in area elif args[0] == "**": - targets = [ - c - for c in client.area.clients - ] + targets = [c for c in client.area.clients] # Kick everyone in hub elif args[0] == "***": - targets = [ - c - for c in client.area.area_manager.clients - ] + targets = [c for c in client.area.area_manager.clients] else: # Try to find by char name first - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, args[0] - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, args[0]) # If that doesn't work, find by client ID if len(targets) == 0 and args[0].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]) - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0])) # If that doesn't work, find by OOC Name if len(targets) == 0: - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, args[0] - ) + targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, args[0]) if len(targets) == 0: - client.send_ooc( - f"No targets found by search term '{args[0]}'." - ) + client.send_ooc(f"No targets found by search term '{args[0]}'.") return try: for c in targets: # We're a puny CM, we can't do this. - if ( - not client.is_mod - and client not in client.area.area_manager.owners - and c not in client.area.clients - ): - raise ArgumentError( - "You can't kick someone from another area as a CM!" - ) + if not client.is_mod and client not in client.area.area_manager.owners and c not in client.area.clients: + raise ArgumentError("You can't kick someone from another area as a CM!") if len(args) == 1: area = client.area else: try: - area = client.area.area_manager.get_area_by_id( - int(args[1])) + area = client.area.area_manager.get_area_by_id(int(args[1])) except AreaError: raise - if ( - not client.is_mod - and client not in client.area.area_manager.owners - and client not in area.owners - ): - raise ArgumentError( - "You can't kick someone to an area you don't own as a CM!" - ) + if not client.is_mod and client not in client.area.area_manager.owners and client not in area.owners: + raise ArgumentError("You can't kick someone to an area you don't own as a CM!") target_pos = "" old_area = c.area if len(args) >= 3: target_pos = args[2] c.set_area(area, target_pos) - c.send_ooc( - f"You were kicked from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}." - ) - database.log_area( - "area_kick", client, client.area, target=c, message=area.id - ) + c.send_ooc(f"You were kicked from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}.") + database.log_area("area_kick", client, client.area, target=c, message=area.id) client.area.invite_list.discard(c.id) client.send_ooc( f"Kicked [{c.id}] {c.showname} from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}." @@ -602,9 +506,7 @@ def ooc_cmd_pos_lock(client, arg): pos = ", ".join(str(p_lock) for p_lock in client.area.pos_lock) client.area.broadcast_ooc(f"Locked pos into {pos}.") - client.area.send_command( - "SD", "*".join(client.area.pos_lock) - ) # set that juicy pos dropdown + client.area.send_command("SD", "*".join(client.area.pos_lock)) # set that juicy pos dropdown @mod_only(area_owners=True) @@ -623,9 +525,7 @@ def ooc_cmd_knock(client, arg): Usage: /knock """ if arg == "": - raise ArgumentError( - "Failed to knock: you need to input an accessible area name or ID to knock!" - ) + raise ArgumentError("Failed to knock: you need to input an accessible area name or ID to knock!") if client.blinded: raise ClientError("Failed to knock: you are blinded!") try: @@ -645,43 +545,26 @@ def ooc_cmd_knock(client, arg): if not allowed and area != client.area: if len(client.area.links) > 0: if str(area.id) not in client.area.links: - raise ClientError( - f"Failed to knock on [{area.id}] {area.name}: That area is inaccessible!" - ) + raise ClientError(f"Failed to knock on [{area.id}] {area.name}: That area is inaccessible!") if str(area.id) in client.area.links: # Get that link reference link = client.area.links[str(area.id)] # Link requires us to be inside a piece of evidence - if ( - len(link["evidence"]) > 0 - and client.hidden_in not in link["evidence"] - ): - raise ClientError( - f"Failed to knock on [{area.id}] {area.name}: That area is inaccessible!" - ) + if len(link["evidence"]) > 0 and client.hidden_in not in link["evidence"]: + raise ClientError(f"Failed to knock on [{area.id}] {area.name}: That area is inaccessible!") if client.area.locked and client.id not in client.area.invite_list: - raise ClientError( - f"Failed to knock on [{area.id}] {area.name}: Current area is locked!" - ) + raise ClientError(f"Failed to knock on [{area.id}] {area.name}: Current area is locked!") area.send_command("RT", "knock") if area == client.area: - area.broadcast_ooc( - f"💢 [{client.id}] {client.showname} knocks for attention. 💢" - ) + area.broadcast_ooc(f"💢 [{client.id}] {client.showname} knocks for attention. 💢") else: - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} knocks on [{area.id}] {area.name}." - ) - area.broadcast_ooc( - f"💢 Someone is knocking from [{client.area.id}] {client.area.name} 💢" - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} knocks on [{area.id}] {area.name}.") + area.broadcast_ooc(f"💢 Someone is knocking from [{client.area.id}] {client.area.name} 💢") except ValueError: - raise ArgumentError( - "Failed to knock: you need to input an accessible area name or ID to knock!" - ) + raise ArgumentError("Failed to knock: you need to input an accessible area name or ID to knock!") except (AreaError, ClientError): raise @@ -692,9 +575,7 @@ def ooc_cmd_peek(client, arg): Usage: /peek """ if arg == "": - raise ArgumentError( - "You need to input an accessible area name or ID to peek into it!" - ) + raise ArgumentError("You need to input an accessible area name or ID to peek into it!") if client.blinded: raise ClientError("You are blinded!") try: @@ -720,12 +601,7 @@ def ooc_cmd_peek(client, arg): if area.dark: raise ClientError("Area is dark!") except ClientError as ex: - if ( - not client.area.dark - and not client.sneaking - and not client.hidden - and "locked" in str(ex).lower() - ): + if not client.area.dark and not client.sneaking and not client.hidden and "locked" in str(ex).lower(): client.area.broadcast_ooc( f"[{client.id}] {client.showname} tried to peek into [{area.id}] {area.name} but {str(ex).lower()}" ) @@ -733,8 +609,7 @@ def ooc_cmd_peek(client, arg): area.broadcast_ooc( f"Someone tried to enter from [{client.area.id}] {client.area.name} but {str(ex).lower()}" ) - client.send_ooc( - f"Failed to peek into [{area.id}] {area.name}: {ex}") + client.send_ooc(f"Failed to peek into [{area.id}] {area.name}: {ex}") return else: sorted_clients = [] @@ -742,9 +617,7 @@ def ooc_cmd_peek(client, arg): if not c.hidden and c not in area.owners and not c.is_mod: # pure IC sorted_clients.append(c) - _sort = [ - c.showname for c in sorted(sorted_clients, key=lambda x: x.showname) - ] + _sort = [c.showname for c in sorted(sorted_clients, key=lambda x: x.showname)] # this would be nice to be a separate "make human readable list" func if len(_sort) == 2: @@ -759,17 +632,12 @@ def ooc_cmd_peek(client, arg): sorted_clients = "nobody" if not client.sneaking and not client.hidden: - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} peeks into [{area.id}] {area.name}..." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} peeks into [{area.id}] {area.name}...") else: - client.send_ooc( - f"You silently peek into [{area.id}] {area.name}...") + client.send_ooc(f"You silently peek into [{area.id}] {area.name}...") client.send_ooc(f"There's {sorted_clients}.") except ValueError: - raise ArgumentError( - "Failed to peek: you need to input an accessible area name or ID to knock!" - ) + raise ArgumentError("Failed to peek: you need to input an accessible area name or ID to knock!") except (AreaError, ClientError): raise @@ -781,9 +649,7 @@ def ooc_cmd_max_players(client, arg): Usage: /max_players [num] """ if arg == "": - client.send_ooc( - f"Max amount of players for the area is {client.area.max_players}." - ) + client.send_ooc(f"Max amount of players for the area is {client.area.max_players}.") return try: @@ -791,12 +657,9 @@ def ooc_cmd_max_players(client, arg): if arg < -1 or arg > 99: raise ClientError("The min-max values are -1 and 99!") client.area.max_players = arg - client.send_ooc( - f"New max amount of players for the area is now {client.area.max_players}." - ) + client.send_ooc(f"New max amount of players for the area is now {client.area.max_players}.") except ValueError: - raise ArgumentError( - "Area ID must be a name, abbreviation or a number.") + raise ArgumentError("Area ID must be a name, abbreviation or a number.") except (AreaError, ClientError): raise @@ -817,11 +680,7 @@ def ooc_cmd_desc(client, arg): else: if client.area.cannot_ic_interact(client): raise ClientError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") arg = arg.strip() if arg == "": @@ -836,9 +695,7 @@ def ooc_cmd_desc(client, arg): desc = arg[:128] if len(arg) > len(desc): desc += "... Use /desc to read the rest." - client.area.broadcast_ooc( - f"📃{client.showname} changed the area description to: {desc}." - ) + client.area.broadcast_ooc(f"📃{client.showname} changed the area description to: {desc}.") client.area.broadcast_area_desc() database.log_area("desc.change", client, client.area, message=arg) @@ -852,11 +709,7 @@ def ooc_cmd_desc_clear(client, arg): raise ClientError("You are blinded!") if client.area.cannot_ic_interact(client): raise ClientError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") if client.area.dark: if not client.is_mod and client not in client.area.owners: @@ -864,12 +717,11 @@ def ooc_cmd_desc_clear(client, arg): client.area.desc_dark = "" else: client.area.desc = "" - client.area.broadcast_ooc( - f"📃{client.showname} cleared the area description." - ) + client.area.broadcast_ooc(f"📃{client.showname} cleared the area description.") client.area.broadcast_area_desc() database.log_area("desc.clear", client, client.area) + @mod_only(area_owners=True) def ooc_cmd_edit_ambience(client, arg): """ @@ -878,9 +730,7 @@ def ooc_cmd_edit_ambience(client, arg): Usage: /edit_ambience [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.edit_ambience = True @@ -906,9 +756,7 @@ def ooc_cmd_lights(client, arg): Usage: /lights [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.area.dark = False @@ -979,4 +827,4 @@ def ooc_cmd_clear_area_broadcast(client, arg): client.send_ooc("Current area broadcast list is already empty!") return client.area.broadcast_list.clear() - client.send_ooc("Current area broadcast list has been cleared.") \ No newline at end of file + client.send_ooc("Current area broadcast list has been cleared.") diff --git a/server/commands/battle.py b/server/commands/battle.py index cdfb5b8f..e8a06d32 100644 --- a/server/commands/battle.py +++ b/server/commands/battle.py @@ -88,7 +88,7 @@ def send_info_fighter(client): msg += f"- {effect}\n" msg += "\n" client.send_ooc(msg) - + def send_stats_fighter(client): """ @@ -108,9 +108,7 @@ def ooc_cmd_choose_fighter(client, arg): Usage: /choose_fighter NameFighter """ if f"{arg.lower()}.yaml" in os.listdir("storage/battlesystem"): - with open( - f"storage/battlesystem/{arg.lower()}.yaml", "r", encoding="utf-8" - ) as c_load: + with open(f"storage/battlesystem/{arg.lower()}.yaml", "r", encoding="utf-8") as c_load: char = yaml.safe_load(c_load) client.battle = ClientManager.BattleChar(client, arg.lower(), char) send_info_fighter(client) @@ -137,15 +135,11 @@ def ooc_cmd_create_fighter(client, arg): args = shlex.split(arg) if len(args) > 8: - client.send_ooc( - "Too many arguments...\nUsage: /create_fighter FighterName HP MANA ATK DEF SPA SPD SPE" - ) + client.send_ooc("Too many arguments...\nUsage: /create_fighter FighterName HP MANA ATK DEF SPA SPD SPE") return if len(args) < 8: - client.send_ooc( - "Not enough arguments...\nUsage: /create_fighter FighterName HP MANA ATK DEF SPA SPD SPE" - ) + client.send_ooc("Not enough arguments...\nUsage: /create_fighter FighterName HP MANA ATK DEF SPA SPD SPE") return if ( @@ -196,9 +190,7 @@ def ooc_cmd_create_move(client, arg): Usage: /create_move MoveName ManaCost MovesType Power Accuracy Effects """ if client.battle is None: - client.send_ooc( - "You have to choose a figher to create a move.\n /choose_fighter FighterName" - ) + client.send_ooc("You have to choose a figher to create a move.\n /choose_fighter FighterName") return args = shlex.split(arg) @@ -233,9 +225,7 @@ def ooc_cmd_create_move(client, arg): ) return - with open( - f"storage/battlesystem/{client.battle.fighter}.yaml", "r", encoding="utf-8" - ) as c_load: + with open(f"storage/battlesystem/{client.battle.fighter}.yaml", "r", encoding="utf-8") as c_load: char = yaml.safe_load(c_load) move_list = [] @@ -281,35 +271,26 @@ def ooc_cmd_modify_stat(client, arg): Usage: /modify_stat FighterName Stat Value """ args = shlex.split(arg) - - + path = derelative(args[0].lower()) if f"{path}.yaml" not in os.listdir("storage/battlesystem"): client.send_ooc("No fighter has this name!") return if args[1].lower() not in ["hp", "mana", "atk", "def", "spa", "spd", "spe"]: - client.send_ooc( - "You could just modify this stats:\nhp, mana, atk, def, spa, spd, spe" - ) + client.send_ooc("You could just modify this stats:\nhp, mana, atk, def, spa, spd, spe") return if float(args[2]) < 0: client.send_ooc("The value have to be a number greater than or equal to zero") return - with open( - f"storage/battlesystem/{path}.yaml", "r", encoding="utf-8" - ) as c_load: + with open(f"storage/battlesystem/{path}.yaml", "r", encoding="utf-8") as c_load: char = yaml.safe_load(c_load) char[args[1].upper()] = float(args[2]) - with open( - f"storage/battlesystem/{path}.yaml", "w", encoding="utf-8" - ) as c_save: + with open(f"storage/battlesystem/{path}.yaml", "w", encoding="utf-8") as c_save: yaml.dump(char, c_save) - client.send_ooc( - f"{path}'s {args[1]} has been modified. To check the changes choose again this fighter" - ) + client.send_ooc(f"{path}'s {args[1]} has been modified. To check the changes choose again this fighter") @mod_only(hub_owners=True) @@ -332,14 +313,12 @@ def ooc_cmd_delete_move(client, arg): You have to choose a fighter first! Usage: /delete_move MoveName """ - + if client.battle is None: client.send_ooc("You have to choose the fighter first") return - - with open( - f"storage/battlesystem/{client.battle.fighter}.yaml", "r", encoding="utf-8" - ) as c_load: + + with open(f"storage/battlesystem/{client.battle.fighter}.yaml", "r", encoding="utf-8") as c_load: char = yaml.safe_load(c_load) move_list = [] for i in range(0, len(char["Moves"])): @@ -354,9 +333,7 @@ def ooc_cmd_delete_move(client, arg): ) as c_save: yaml.dump(char, c_save) - client.battle = ClientManager.BattleChar( - client, client.battle.fighter, char - ) + client.battle = ClientManager.BattleChar(client, client.battle.fighter, char) guild = None for g in client.area.battle_guilds: if client in client.area.battle_guilds[g]: @@ -370,49 +347,49 @@ def ooc_cmd_delete_move(client, arg): @mod_only(hub_owners=True) def ooc_cmd_battle_config(client, arg): - """ - Allow you to customize some battle settings. - Usage: /custom_battle - """ - if arg == "": - client.send_ooc( - "paralysis_rate, critical_rate, critical_bonus, bonus_malus, poison_damage, show_hp, min_multishot, max_multishot, burn_damage, freeze_damage, confusion_rate, enraged_bonus, stolen_stat" - ) - return - args = arg.split(" ") - if args[0].lower() == "paralysis_rate": - client.area.battle_paralysis_rate = int(args[1]) - elif args[0].lower() == "critical_rate": - client.area.battle_critical_rate = int(args[1]) - elif args[0].lower() == "critical_bonus": - client.area.battle_critical_bonus = float(args[1]) - elif args[0].lower() == "bonus_malus": - client.area.battle_bonus_malus = float(args[1]) - elif args[0].lower() == "poison_damage": - client.area.battle_poison_damage = float(args[1]) - elif args[0].lower() == "min_multishot": - client.area.battle_min_multishot = int(args[1]) - elif args[0].lower() == "max_multishot": - client.area.battle_max_multishot = int(args[1]) - elif args[0].lower() == "burn_damage": - client.area.battle_burn_damage = float(args[1]) - elif args[0].lower() == "freeze_damage": - client.area.battle_freeze_damage = float(args[1]) - elif args[0].lower() == "confusion_rate": - client.area.battle_confusion_rate = int(args[1]) - elif args[0].lower() == "enraged_bonus": - client.area.battle_enraged_bonus = float(args[1]) - elif args[0].lower() == "stolen_stat": - client.area.battle_stolen_stat = float(args[1]) - elif args[1].lower() in ["true", "false"] and args[0].lower() == "show_hp": - if args[1].lower() == "true": - client.area.battle_show_hp = True - else: - client.area.battle_show_hp = False - else: - client.send_ooc("value is not valid") - return - client.send_ooc(f"{args[0].lower()} has been changed to {args[1]}") + """ + Allow you to customize some battle settings. + Usage: /custom_battle + """ + if arg == "": + client.send_ooc( + "paralysis_rate, critical_rate, critical_bonus, bonus_malus, poison_damage, show_hp, min_multishot, max_multishot, burn_damage, freeze_damage, confusion_rate, enraged_bonus, stolen_stat" + ) + return + args = arg.split(" ") + if args[0].lower() == "paralysis_rate": + client.area.battle_paralysis_rate = int(args[1]) + elif args[0].lower() == "critical_rate": + client.area.battle_critical_rate = int(args[1]) + elif args[0].lower() == "critical_bonus": + client.area.battle_critical_bonus = float(args[1]) + elif args[0].lower() == "bonus_malus": + client.area.battle_bonus_malus = float(args[1]) + elif args[0].lower() == "poison_damage": + client.area.battle_poison_damage = float(args[1]) + elif args[0].lower() == "min_multishot": + client.area.battle_min_multishot = int(args[1]) + elif args[0].lower() == "max_multishot": + client.area.battle_max_multishot = int(args[1]) + elif args[0].lower() == "burn_damage": + client.area.battle_burn_damage = float(args[1]) + elif args[0].lower() == "freeze_damage": + client.area.battle_freeze_damage = float(args[1]) + elif args[0].lower() == "confusion_rate": + client.area.battle_confusion_rate = int(args[1]) + elif args[0].lower() == "enraged_bonus": + client.area.battle_enraged_bonus = float(args[1]) + elif args[0].lower() == "stolen_stat": + client.area.battle_stolen_stat = float(args[1]) + elif args[1].lower() in ["true", "false"] and args[0].lower() == "show_hp": + if args[1].lower() == "true": + client.area.battle_show_hp = True + else: + client.area.battle_show_hp = False + else: + client.send_ooc("value is not valid") + return + client.send_ooc(f"{args[0].lower()} has been changed to {args[1]}") def send_battle_info(client): @@ -473,11 +450,7 @@ def ooc_cmd_fight(client, arg): Usage: /fight """ if len(client.area.fighters) > 0 and client.area.battle_started: - free_fighters = { - c.battle.fighter: c - for c in client.area.fighters - if c.battle.current_client is not None - } + free_fighters = {c.battle.fighter: c for c in client.area.fighters if c.battle.current_client is not None} if client in client.area.fighters: index = client.area.fighters.index(client) client.battle = client.area.fighters[index].battle @@ -497,9 +470,7 @@ def ooc_cmd_fight(client, arg): client.area.fighters.remove(target) client.area.fighters.append(client) msg = send_battle_info(client) - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ is ready to fight (reconnected)" - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ is ready to fight (reconnected)") for client in client.area.fighters: client.send_ooc(msg) return @@ -520,9 +491,7 @@ def ooc_cmd_fight(client, arg): msg = send_battle_info(client) for client in client.area.fighters: client.send_ooc(msg) - client.area.broadcast_ooc( - f"⚔️{client.battle.fighter} ({client.showname}) is ready to fight!⚔️" - ) + client.area.broadcast_ooc(f"⚔️{client.battle.fighter} ({client.showname}) is ready to fight!⚔️") client.area.area_manager.char_list[client.char_id] battle_send_ic(client, msg=f"~{client.battle.fighter}~ is ready to fight") @@ -553,18 +522,14 @@ def ooc_cmd_surrender(client, arg): client.battle.hp = 0 client.battle.selected_move = -1 client.battle.target = None - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ decides to surrend", offset=100 - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ decides to surrend", offset=100) with open( f"storage/battlesystem/{client.battle.fighter}.yaml", "r", encoding="utf-8", ) as c_load: char = yaml.safe_load(c_load) - client.battle = ClientManager.BattleChar( - client, client.battle.fighter, char - ) + client.battle = ClientManager.BattleChar(client, client.battle.fighter, char) guild = None for g in client.area.battle_guilds: if client in client.area.battle_guilds[g]: @@ -603,9 +568,7 @@ def ooc_cmd_remove_fighter(client, arg): encoding="utf-8", ) as c_load: char = yaml.safe_load(c_load) - target.battle = ClientManager.BattleChar( - target, target.battle.fighter, char - ) + target.battle = ClientManager.BattleChar(target, target.battle.fighter, char) guild = None for g in target.area.battle_guilds: if target in target.area.battle_guilds[g]: @@ -739,9 +702,7 @@ def ooc_cmd_leave_guild(client, arg): else: client.send_ooc("Target not found!") elif client == client.area.battle_guilds[client.battle.guild][0]: - guild_ids = { - c.id: c for c in client.area.battle_guilds[client.battle.guild] - } + guild_ids = {c.id: c for c in client.area.battle_guilds[client.battle.guild]} if int(arg) in guild_ids: target = guild_ids[int(arg)] guild = target.battle.guild @@ -773,9 +734,7 @@ def ooc_cmd_join_guild(client, arg): guild = client.battle.guild if client != client.area.battle_guilds[guild][0]: - client.send_ooc( - "You are not the guild leader, you cannot choose who can join to the guild." - ) + client.send_ooc("You are not the guild leader, you cannot choose who can join to the guild.") return area_ids = {c.id: c for c in client.area.clients} @@ -853,9 +812,7 @@ def send_info_guild(client): msg += "\n\n👤Members👤:\n\n" for fighter in client.area.battle_guilds[guild]: if fighter != guild_leader: - msg += ( - f"⚔️[{fighter.id}]{fighter.battle.fighter} ({fighter.showname})⚔️\n" - ) + msg += f"⚔️[{fighter.id}]{fighter.battle.fighter} ({fighter.showname})⚔️\n" client.send_ooc(msg) @@ -996,25 +953,19 @@ def start_battle_animation(area): if client.battle.hp > 0: # check if a fighter skipped the turn if client.battle.selected_move == -2: - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ decides to skip the turn" - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ decides to skip the turn") continue if client.battle.status == "stunned": client.battle.status = None - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ is stunned and cannot fight" - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ is stunned and cannot fight") continue if client.battle.status == "confused": confused = random.randint(1, client.area.battle_confusion_rate) if confused == 1: client.battle.status = None - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ snaps out of confusion" - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ snaps out of confusion") elif confused == client.area.battle_confusion_rate: battle_send_ic( client, @@ -1054,17 +1005,12 @@ def start_battle_animation(area): # check if the fighter misses the move miss = random.randint(1, 100) if move.accuracy < miss: - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ misses the target" - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ misses the target") continue # check if the fighter is paralysed paralysis = random.randint(1, area.battle_paralysis_rate) - if ( - paralysis == area.battle_paralysis_rate - and client.battle.status == "paralysis" - ): + if paralysis == area.battle_paralysis_rate and client.battle.status == "paralysis": battle_send_ic( client, msg=f"~{client.battle.fighter}~ is affected by paralysis and cannot fight", @@ -1088,18 +1034,14 @@ def start_battle_animation(area): targets = [c for c in client.area.fighters] else: targets = [ - c - for c in client.area.battle_guilds[client.battle.guild] - if c in client.area.fighters + c for c in client.area.battle_guilds[client.battle.guild] if c in client.area.fighters ] else: if client.battle.guild is None: targets = [c for c in client.area.fighters if c != client] else: targets = [ - c - for c in client.area.fighters - if c not in client.area.battle_guilds[client.battle.guild] + c for c in client.area.fighters if c not in client.area.battle_guilds[client.battle.guild] ] if "multishot" in move.effect: shots = random.randint( @@ -1108,9 +1050,7 @@ def start_battle_animation(area): ) targets = random.choices(targets, shots) elif "multishot" in move.effect and "atkall" not in move.effect: - shots = random.randint( - client.area.battle_min_multishot, client.area.battle_max_multishot - ) + shots = random.randint(client.area.battle_min_multishot, client.area.battle_max_multishot) targets = [] for i in range(0, shots): targets.append(client.battle.target) @@ -1156,7 +1096,7 @@ def start_battle_animation(area): msg=f"and heals ~{target.battle.fighter}~ of ~{heal}~ hp", effect="lifeup", ) - + if "healstatus" in move.effect: if target.battle.status is None: battle_send_ic( @@ -1173,12 +1113,8 @@ def start_battle_animation(area): f"and removed {status} from ~{target.battle.fighter}~", ) if status == "burn": - target.battle.spd = ( - target.battle.spd * area.battle_bonus_malus - ) - target.battle.defe = ( - target.battle.defe * area.battle_bonus_malus - ) + target.battle.spd = target.battle.spd * area.battle_bonus_malus + target.battle.defe = target.battle.defe * area.battle_bonus_malus if "atkraiseally" in move.effect: target.battle.atk = target.battle.atk * area.battle_bonus_malus @@ -1189,9 +1125,7 @@ def start_battle_animation(area): ) if "defraiseally" in move.effect: - target.battle.defe = ( - target.battle.defe * area.battle_bonus_malus - ) + target.battle.defe = target.battle.defe * area.battle_bonus_malus battle_send_ic( target, msg=f" and raises the defense of ~{target.battle.fighter}~", @@ -1392,9 +1326,7 @@ def start_battle_animation(area): shake=1, ) target.battle.spd = target.battle.spd / area.battle_bonus_malus - target.battle.defe = ( - target.battle.defe / area.battle_bonus_malus - ) + target.battle.defe = target.battle.defe / area.battle_bonus_malus battle_send_ic( target, msg=f"and ~{target.battle.fighter}~'s defensive statistics go down", @@ -1526,9 +1458,7 @@ def start_battle_animation(area): "burn", "freeze", ]: - battle_send_ic( - client, msg=f"~{client.battle.fighter}~ ran out of hp!", offset=100 - ) + battle_send_ic(client, msg=f"~{client.battle.fighter}~ ran out of hp!", offset=100) # check dead fighters and unselect move and target for client in list(area.fighters): @@ -1545,9 +1475,7 @@ def start_battle_animation(area): encoding="utf-8", ) as c_load: char = yaml.safe_load(c_load) - client.battle = ClientManager.BattleChar( - client, client.battle.fighter, char - ) + client.battle = ClientManager.BattleChar(client, client.battle.fighter, char) guild = None for g in client.area.battle_guilds: if client in client.area.battle_guilds[g]: @@ -1559,13 +1487,9 @@ def start_battle_animation(area): if len(area.fighters) == 1: winner = area.fighters[0] battle_send_ic(winner, msg=f"~{winner.battle.fighter}~ wins the battle!") - with open( - f"storage/battlesystem/{winner.battle.fighter}.yaml", "r", encoding="utf-8" - ) as c_load: + with open(f"storage/battlesystem/{winner.battle.fighter}.yaml", "r", encoding="utf-8") as c_load: char = yaml.safe_load(c_load) - winner.battle = ClientManager.BattleChar( - winner, winner.battle.fighter, char - ) + winner.battle = ClientManager.BattleChar(winner, winner.battle.fighter, char) guild = None for g in winner.area.battle_guilds: if winner in winner.area.battle_guilds[g]: @@ -1592,9 +1516,7 @@ def start_battle_animation(area): encoding="utf-8", ) as c_load: char = yaml.safe_load(c_load) - winner.battle = ClientManager.BattleChar( - winner, winner.battle.fighter, char - ) + winner.battle = ClientManager.BattleChar(winner, winner.battle.fighter, char) guild = None for g in winner.area.battle_guilds: if winner in winner.area.battle_guilds[g]: diff --git a/server/commands/casing.py b/server/commands/casing.py index 10d61f07..2cbd66c8 100644 --- a/server/commands/casing.py +++ b/server/commands/casing.py @@ -51,6 +51,7 @@ "ooc_cmd_evidence_lists", ] + def ooc_cmd_doc(client, arg): """ Show or change the link for the current case document. @@ -62,16 +63,10 @@ def ooc_cmd_doc(client, arg): else: if client.area.cannot_ic_interact(client): raise ClientError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") client.area.change_doc(arg) - client.area.broadcast_ooc( - f"{client.showname} changed the doc link to: {client.area.doc}" - ) + client.area.broadcast_ooc(f"{client.showname} changed the doc link to: {client.area.doc}") database.log_area("doc.change", client, client.area, message=arg) @@ -84,15 +79,10 @@ def ooc_cmd_cleardoc(client, arg): raise ArgumentError("This command has no arguments.") if client.area.cannot_ic_interact(client): raise ClientError("You are not on the area's invite list!") - if ( - not client.is_mod - and client not in client.area.owners - and client.char_id == -1 - ): + if not client.is_mod and client not in client.area.owners and client.char_id == -1: raise ClientError("You may not do that while spectating!") client.area.change_doc() - client.area.broadcast_ooc( - "{} cleared the doc link.".format(client.showname)) + client.area.broadcast_ooc("{} cleared the doc link.".format(client.showname)) database.log_area("doc.clear", client, client.area) @@ -126,9 +116,7 @@ def ooc_cmd_evidence(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/evidence {arg})" - ) + raise AreaError(f"Target evidence not found! (/evidence {arg})") msg = f"==💼[{i+1}]: '{evidence[0]}==" msg += f"\n🖼️Image: {evidence[2]}" msg += f"\n📃Desc:\n{evidence[1]}" @@ -152,7 +140,8 @@ def ooc_cmd_evidence_add(client, arg): args = shlex.split(arg) if len(args) > 3: raise ArgumentError( - f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/evidence_add {arg})") + f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/evidence_add {arg})" + ) # fill the rest of it with asterisk to fill to max_args args = args + ([""] * (max_args - len(args))) if args[0] == "": @@ -162,12 +151,10 @@ def ooc_cmd_evidence_add(client, arg): if args[2] == "": args[2] = "empty.png" except ValueError as ex: - client.send_ooc(f'{ex} (/evidence_add {arg})') + client.send_ooc(f"{ex} (/evidence_add {arg})") return - client.area.evi_list.add_evidence( - client, args[0], args[1], args[2] - ) + client.area.evi_list.add_evidence(client, args[0], args[1], args[2]) database.log_area("evidence.add", client, client.area) client.area.broadcast_evidence_list() client.send_ooc(f"You have added evidence '{args[0]}'.") @@ -179,9 +166,7 @@ def ooc_cmd_evidence_remove(client, arg): Usage: /evidence_remove """ if arg == "": - raise ArgumentError( - "Use /evidence_remove to remove that piece of evidence." - ) + raise ArgumentError("Use /evidence_remove to remove that piece of evidence.") try: evi_list = client.area.get_evidence_list(client) evidence = None @@ -190,9 +175,7 @@ def ooc_cmd_evidence_remove(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/evidence_remove {arg})" - ) + raise AreaError(f"Target evidence not found! (/evidence_remove {arg})") evi_name = evidence[0] client.area.evi_list.del_evidence(client, i) database.log_area("evidence.del", client, client.area) @@ -212,9 +195,7 @@ def ooc_cmd_evidence_edit(client, arg): Usage: /evidence_edit [name] [desc] [image] """ if arg == "": - raise ArgumentError( - "Use /evidence_edit [name] [desc] [image] to edit that piece of evidence." - ) + raise ArgumentError("Use /evidence_edit [name] [desc] [image] to edit that piece of evidence.") try: max_args = 4 @@ -222,11 +203,12 @@ def ooc_cmd_evidence_edit(client, arg): args = shlex.split(arg) if len(args) > 4: raise ArgumentError( - f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/evidence_edit {arg})") + f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/evidence_edit {arg})" + ) # fill the rest of it with asterisk to fill to max_args args = args + (["*"] * (max_args - len(args))) except ValueError as ex: - client.send_ooc(f'{ex} (/evidence_edit {arg})') + client.send_ooc(f"{ex} (/evidence_edit {arg})") return try: @@ -237,9 +219,7 @@ def ooc_cmd_evidence_edit(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/evidence_edit {arg})" - ) + raise AreaError(f"Target evidence not found! (/evidence_edit {arg})") evi_name = evidence[0] evi = (args[1], args[2], args[3], "all") @@ -247,8 +227,7 @@ def ooc_cmd_evidence_edit(client, arg): database.log_area("evidence.edit", client, client.area) client.area.broadcast_evidence_list() if evi[0] != "*" and evi_name != evi[0]: - client.send_ooc( - f"You have edited evidence '{evi_name}' to '{evi[0]}'.") + client.send_ooc(f"You have edited evidence '{evi_name}' to '{evi[0]}'.") else: client.send_ooc(f"You have edited evidence '{evi_name}'.") except ValueError: @@ -278,12 +257,9 @@ def ooc_cmd_evidence_present(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/evidence_present {arg})" - ) + raise AreaError(f"Target evidence not found! (/evidence_present {arg})") client.presenting = i + 1 - client.send_ooc( - f"Will now present evidence [{client.presenting}] {evidence[0]} on next IC message.") + client.send_ooc(f"Will now present evidence [{client.presenting}] {evidence[0]} on next IC message.") except ValueError: raise except (AreaError, ClientError): @@ -302,21 +278,15 @@ def ooc_cmd_evidence_mod(client, arg): elif arg in ["FFA", "Mods", "CM", "HiddenCM"]: if not client.is_mod: if client.area.evidence_mod == "Mods": - raise ClientError( - "You must be authorized to change this area's evidence mod from Mod-only." - ) + raise ClientError("You must be authorized to change this area's evidence mod from Mod-only.") if arg == "Mods": - raise ClientError( - "You must be authorized to set the area's evidence to Mod-only." - ) + raise ClientError("You must be authorized to set the area's evidence to Mod-only.") client.area.evidence_mod = arg client.area.broadcast_evidence_list() client.send_ooc(f"current evidence mod: {client.area.evidence_mod}") database.log_area("evidence_mod", client, client.area, message=arg) else: - raise ArgumentError( - "Wrong Argument. Use /evidence_mod . Possible values: FFA, CM, Mods, HiddenCM" - ) + raise ArgumentError("Wrong Argument. Use /evidence_mod . Possible values: FFA, CM, Mods, HiddenCM") @mod_only(area_owners=True) @@ -331,8 +301,7 @@ def ooc_cmd_evidence_swap(client, arg): if len(args) != 2: raise ClientError("you must specify 2 numbers") try: - client.area.evi_list.evidence_swap( - client, int(args[0]) - 1, int(args[1]) - 1) + client.area.evi_list.evidence_swap(client, int(args[0]) - 1, int(args[1]) - 1) client.area.broadcast_evidence_list() except Exception: raise ClientError("you must specify 2 numbers") @@ -351,16 +320,13 @@ def ooc_cmd_cm(client, arg): if arg != "": # Nominate all self clients (Those not present in area will not be counted later) if arg == "*": - arg = [c.id for c in client.server.client_manager.get_multiclients( - client.ipid, client.hdid)] + arg = [c.id for c in client.server.client_manager.get_multiclients(client.ipid, client.hdid)] # CM the provided targets else: arg = arg.split(" ") # Client is not a mod, not a CM and not a GM, meaning they're trying to nominate someone without being /cm first if not client.is_mod and client not in client.area.owners: - raise ArgumentError( - "You cannot 'nominate' people to be CMs when you are not one." - ) + raise ArgumentError("You cannot 'nominate' people to be CMs when you are not one.") else: # Self CM arg = [client.id] @@ -368,16 +334,11 @@ def ooc_cmd_cm(client, arg): for id in arg: try: id = int(id) - c = client.server.client_manager.get_targets( - client, TargetType.ID, id, False - )[0] + c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] if c not in client.area.clients: - raise ArgumentError( - "You can only 'nominate' people to be CMs when they are in the area." - ) + raise ArgumentError("You can only 'nominate' people to be CMs when they are in the area.") elif c in client.area._owners: - client.send_ooc( - f"{c.showname} [{c.id}] is already a CM here.") + client.send_ooc(f"{c.showname} [{c.id}] is already a CM here.") else: client.area.add_owner(c) database.log_area("cm.add", client, client.area, target=c) @@ -403,16 +364,12 @@ def ooc_cmd_uncm(client, arg): for _id in arg: try: _id = int(_id) - c = client.server.client_manager.get_targets( - client, TargetType.ID, _id, False - )[0] + c = client.server.client_manager.get_targets(client, TargetType.ID, _id, False)[0] if c in client.area._owners: client.area.remove_owner(c) database.log_area("cm.remove", client, client.area, target=c) else: - client.send_ooc( - "You cannot remove someone from CMing when they aren't a CM." - ) + client.send_ooc("You cannot remove someone from CMing when they aren't a CM.") except (ValueError, IndexError): client.send_ooc(f"{_id} does not look like a valid ID.") except (ClientError, ArgumentError): @@ -448,15 +405,12 @@ def ooc_cmd_anncase(client, arg): # XXX: Merge with aoprotocol.net_cmd_casea if client in client.area.owners: if not client.can_call_case(): - raise ClientError( - "Please wait 60 seconds between case announcements!") + raise ClientError("Please wait 60 seconds between case announcements!") args = re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', arg) if len(args) == 0: raise ArgumentError("Please do not call this command manually!") elif len(args) == 1: - raise ArgumentError( - "You should probably announce the case to at least one person." - ) + raise ArgumentError("You should probably announce the case to at least one person.") else: if ( not args[1] == "1" @@ -465,9 +419,7 @@ def ooc_cmd_anncase(client, arg): and not args[4] == "1" and not args[5] == "1" ): - raise ArgumentError( - "You should probably announce the case to at least one person." - ) + raise ArgumentError("You should probably announce the case to at least one person.") msg = "=== Case Announcement ===\r\n{} [{}] is hosting {}, looking for ".format( client.showname, client.id, args[0] ) @@ -483,21 +435,14 @@ def ooc_cmd_anncase(client, arg): msg += ", ".join(lookingfor) + ".\r\n==================" - client.server.send_all_cmd_pred( - "CASEA", msg, args[1], args[2], args[3], args[4], args[5], "1" - ) + client.server.send_all_cmd_pred("CASEA", msg, args[1], args[2], args[3], args[4], args[5], "1") client.set_case_call_delay() - log_data = { - k: v - for k, v in zip(("message", "def", "pro", "jud", "jur", "steno"), args) - } + log_data = {k: v for k, v in zip(("message", "def", "pro", "jud", "jur", "steno"), args)} database.log_area("case", client, client.area, message=log_data) else: - raise ClientError( - "You cannot announce a case in an area where you are not a CM!" - ) + raise ClientError("You cannot announce a case in an area where you are not a CM!") @mod_only() @@ -510,9 +455,7 @@ def ooc_cmd_blockwtce(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target. Use /blockwtce .") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must enter a number. Use /blockwtce .") if not targets: @@ -531,12 +474,9 @@ def ooc_cmd_unblockwtce(client, arg): Usage: /unblockwtce """ if len(arg) == 0: - raise ArgumentError( - "You must specify a target. Use /unblockwtce .") + raise ArgumentError("You must specify a target. Use /unblockwtce .") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must enter a number. Use /unblockwtce .") if not targets: @@ -563,9 +503,7 @@ def ooc_cmd_judgelog(client, arg): jlog_msg += f"\r\n{x}" client.send_ooc(jlog_msg) else: - raise ServerError( - "There have been no judge actions in this area since start of session." - ) + raise ServerError("There have been no judge actions in this area since start of session.") def ooc_cmd_afk(client, arg): @@ -590,9 +528,7 @@ def ooc_cmd_remote_listen(client, arg): try: client.remote_listen = options[arg.upper()] except KeyError: - raise ArgumentError( - "Invalid option! Your options are NONE, IC, OOC or ALL." - ) + raise ArgumentError("Invalid option! Your options are NONE, IC, OOC or ALL.") reversed_options = dict(map(reversed, options.items())) opt = reversed_options[client.remote_listen] client.send_ooc(f"Your current remote listen option is: {opt}") @@ -615,9 +551,7 @@ def ooc_cmd_testimony(client, arg): return idx = int(args[0]) - 1 client.area.testimony_send(idx) - client.area.broadcast_ooc( - f"{client.showname} has moved to Statement {idx+1}." - ) + client.area.broadcast_ooc(f"{client.showname} has moved to Statement {idx+1}.") except ValueError: raise ArgumentError("Index must be a number!") except ClientError: @@ -647,8 +581,7 @@ def ooc_cmd_testimony_start(client, arg): Usage: /testimony_start """ if arg == "": - raise ArgumentError( - "You must provite a title! /testimony_start <title>.") + raise ArgumentError("You must provite a title! /testimony_start <title>.") if len(arg) < 3: raise ArgumentError("Title must contain at least 3 characters!") client.area.testimony.clear() @@ -687,8 +620,7 @@ def ooc_cmd_testimony_clear(client, arg): raise ArgumentError("This command does not take any arguments.") client.area.testimony.clear() client.area.testimony_title = "" - client.area.broadcast_ooc( - f"{client.showname} cleared the current testimony.") + client.area.broadcast_ooc(f"{client.showname} cleared the current testimony.") @mod_only(area_owners=True) @@ -708,8 +640,7 @@ def ooc_cmd_testimony_remove(client, arg): client.area.testimony.pop(idx) if client.area.testimony_index == idx: client.area.testimony_index = -1 - client.area.broadcast_ooc( - f"{client.showname} has removed Statement {idx+1}.") + client.area.broadcast_ooc(f"{client.showname} has removed Statement {idx+1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -735,8 +666,7 @@ def ooc_cmd_testimony_amend(client, arg): lst = list(client.area.testimony[idx]) lst[4] = "}}}" + " ".join(args[1:]) client.area.testimony[idx] = tuple(lst) - client.area.broadcast_ooc( - f"{client.showname} has amended Statement {idx+1}.") + client.area.broadcast_ooc(f"{client.showname} has amended Statement {idx+1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -764,9 +694,7 @@ def ooc_cmd_testimony_swap(client, arg): client.area.testimony[idx1], client.area.testimony[idx2], ) - client.area.broadcast_ooc( - f"{client.showname} has swapped Statements {idx1+1} and {idx2+1}." - ) + client.area.broadcast_ooc(f"{client.showname} has swapped Statements {idx1+1} and {idx2+1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -793,9 +721,7 @@ def ooc_cmd_testimony_insert(client, arg): statement = client.area.testimony.pop(idx1) client.area.testimony.insert(idx2, statement) - client.area.broadcast_ooc( - f"{client.showname} has inserted Statement {idx1+1} into {idx2+1}." - ) + client.area.broadcast_ooc(f"{client.showname} has inserted Statement {idx1+1} into {idx2+1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -813,10 +739,7 @@ def ooc_cmd_cs(client, arg): Usage: /cs <id> """ if arg == "": - if ( - client.area.minigame_schedule - and not client.area.minigame_schedule.cancelled() - ): + if client.area.minigame_schedule and not client.area.minigame_schedule.cancelled(): msg = f"Current minigame is {client.area.minigame}!\n" red = [] for cid in client.area.red_team: @@ -842,9 +765,7 @@ def ooc_cmd_cs(client, arg): return args = arg.split() try: - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), True - )[0] + target = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), True)[0] except Exception: raise ArgumentError("Target not found.") else: @@ -920,15 +841,13 @@ def set_minigame_song(client, minigame="", song="", condition=0): client.area.panic_talk_action_song_end = song elif condition == 2: client.area.panic_talk_action_song_concede = song - client.send_ooc( - f"Setting the {minigame} {condition_str} song to {song}.") + client.send_ooc(f"Setting the {minigame} {condition_str} song to {song}.") return # Songname is not provided client.editing_minigame_song = minigame client.editing_minigame_song_condition = condition - client.send_ooc( - f"Play a song to set the {minigame} {condition_str} song to...") + client.send_ooc(f"Play a song to set the {minigame} {condition_str} song to...") @mod_only(area_owners=True) @@ -976,22 +895,17 @@ def ooc_cmd_concede(client, arg): if client.area.minigame != "": try: if arg.lower() == "not-pta" and client.area.minigame == "Panic Talk Action": - client.send_ooc( - "Current minigame is Panic Talk Action - not conceding this one." - ) + client.send_ooc("Current minigame is Panic Talk Action - not conceding this one.") return # CM's end the minigame automatically using /concede if client in client.area.owners: client.area.end_minigame("Forcibly ended.") - client.area.broadcast_ooc( - "The minigame has been forcibly ended.") + client.area.broadcast_ooc("The minigame has been forcibly ended.") return if client.char_id not in client.area.blue_team and client.char_id not in client.area.red_team: client.area.vote_end_minigame(client) return - client.area.start_debate( - client, client - ) # starting a debate against yourself is a concede + client.area.start_debate(client, client) # starting a debate against yourself is a concede except AreaError as ex: raise ex else: @@ -1007,9 +921,7 @@ def ooc_cmd_subtheme(client, arg): client.area.area_manager.subtheme = arg.strip() # Set everyone's subthemes client.area.area_manager.broadcast_subtheme() - client.send_ooc( - f"Setting hub subtheme to {arg}." - ) + client.send_ooc(f"Setting hub subtheme to {arg}.") @mod_only(hub_owners=True) @@ -1021,9 +933,7 @@ def ooc_cmd_time_of_day(client, arg): client.area.area_manager.time_of_day = arg.strip() # Set everyone's time_of_day client.area.area_manager.broadcast_subtheme() - client.send_ooc( - f"Setting hub time_of_day to {arg}." - ) + client.send_ooc(f"Setting hub time_of_day to {arg}.") @mod_only(hub_owners=True) @@ -1040,7 +950,7 @@ def ooc_cmd_evidence_lists(client, arg): client.send_ooc(msg) -def evidence_load(client, name, overlay = False): +def evidence_load(client, name, overlay=False): if f"{name}.yaml" not in os.listdir("storage/evidence"): client.send_ooc(f"Evidence List {name} not found!") return @@ -1080,7 +990,7 @@ def ooc_cmd_evidence_overlay(client, arg): if arg == "": client.send_ooc("Usage: /evidence_overlay <name>") return - evidence_load(client, derelative(arg), overlay = True) + evidence_load(client, derelative(arg), overlay=True) @mod_only(area_owners=True) @@ -1102,12 +1012,8 @@ def ooc_cmd_evidence_save(client, arg): with open(arg, "r", encoding="utf-8") as stream: evi_list = yaml.safe_load(stream) if "read_only" in evi_list and evi_list["read_only"] is True: - raise ArgumentError( - f"Evidence List {arg} already exists and it is read-only!" - ) + raise ArgumentError(f"Evidence List {arg} already exists and it is read-only!") with open(arg, "w", encoding="utf-8") as yaml_save: yaml.dump(evidence, yaml_save) database.log_area("evidence.save", client, client.area, arg) - client.send_ooc( - f"Evidence has been saved as '{arg}' on the server." - ) + client.send_ooc(f"Evidence has been saved as '{arg}' on the server.") diff --git a/server/commands/character.py b/server/commands/character.py index e141b40b..cf117a28 100644 --- a/server/commands/character.py +++ b/server/commands/character.py @@ -77,6 +77,7 @@ def ooc_cmd_set_url(client, arg): client.send_ooc(f"URL set to {arg_strip}") client.char_url = arg_strip + def ooc_cmd_get_urls(client, arg): """ This command returns the server's URL List. @@ -87,6 +88,7 @@ def ooc_cmd_get_urls(client, arg): f_server_links += f"{name}: {url} \n" client.send_ooc(f_server_links) + def ooc_cmd_switch(client, arg): """ Switch to another character. If moderator and the specified character is @@ -108,8 +110,7 @@ def ooc_cmd_switch(client, arg): except ServerError: raise try: - client.change_character( - cid, client.is_mod or client in client.area.owners) + client.change_character(cid, client.is_mod or client in client.area.owners) except ClientError: raise client.send_ooc("Character changed.") @@ -145,9 +146,7 @@ def ooc_cmd_pair(client, arg): return if arg.isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), True) if len(targets) > 0: client.charid_pair = targets[0].char_id client.charid_pair_override = True @@ -161,7 +160,9 @@ def ooc_cmd_pair(client, arg): char = client.charid_pair if client.charid_pair in range(0, len(client.area.area_manager.char_list)): char = client.area.area_manager.char_list[client.charid_pair] - client.send_ooc(f"Successfully paired with '{char}'! Ask them to pair with you back, and show up on the same /pos for it to work.") + client.send_ooc( + f"Successfully paired with '{char}'! Ask them to pair with you back, and show up on the same /pos for it to work." + ) else: client.send_ooc("Pairing target not found!") @@ -186,11 +187,11 @@ def ooc_cmd_pair_order(client, arg): Usage: /pair_order [order] """ if client.charid_pair_override: - msg = ['in front of', 'behind'] + msg = ["in front of", "behind"] if arg: - if arg.lower() == 'front': + if arg.lower() == "front": client.pair_order = 0 - elif arg.lower() == 'behind': + elif arg.lower() == "behind": client.pair_order = 1 elif arg.isdigit() and int(arg) in [0, 1]: client.pair_order = int(arg) @@ -220,17 +221,11 @@ def ooc_cmd_forcepos(client, arg): pos = args[0] if len(args) > 1: - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, " ".join(args[1:]), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, " ".join(args[1:]), True) if len(targets) == 0 and args[1].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[1]), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[1]), True) if len(targets) == 0: - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, " ".join(args[1:]), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, " ".join(args[1:]), True) if len(targets) == 0: raise ArgumentError("No targets found.") else: @@ -258,8 +253,7 @@ def ooc_cmd_forcepos(client, arg): t.area.broadcast_evidence_list() t.send_ooc(f"Forced into /pos {_pos}.") client.send_ooc(f"Forced [{t.id}] {t.showname} into /pos {_pos}.") - database.log_area("forcepos", client, client.area, - target=t, message=pos) + database.log_area("forcepos", client, client.area, target=t, message=pos) except ClientError: raise @@ -271,18 +265,13 @@ def ooc_cmd_force_switch(client, arg): Usage: /force_switch <id> [char] """ if not arg: - raise ArgumentError( - 'Not enough arguments. Usage: /force_switch <id> [char]') + raise ArgumentError("Not enough arguments. Usage: /force_switch <id> [char]") args = shlex.split(arg) try: if args[0].isnumeric(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) else: - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, args[0], False - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, args[0], False) for target in targets: force_switch(client, target, " ".join(args[1:])) except Exception as ex: @@ -293,7 +282,7 @@ def ooc_cmd_force_switch(client, arg): def force_switch(client, target, char=""): if not client.is_mod and client not in target.area.owners: - raise ClientError(f'Insufficient permissions for {char}') + raise ClientError(f"Insufficient permissions for {char}") if char != "": try: if char == "-1" or char.lower() == "spectator": @@ -330,9 +319,7 @@ def ooc_cmd_kill(client, arg): targets = [] ids = [int(s) for s in arg.split(" ")] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: @@ -343,9 +330,8 @@ def ooc_cmd_kill(client, arg): force_switch(client, target, "-1") target.send_ooc("💀You are dead!💀") except Exception as ex: - raise ArgumentError( - f"Error encountered: {ex}. Use /kill <id(s)> as a mod or area owner." - ) + raise ArgumentError(f"Error encountered: {ex}. Use /kill <id(s)> as a mod or area owner.") + def ooc_cmd_randomchar(client, arg): """ @@ -379,18 +365,12 @@ def ooc_cmd_charcurse(client, arg): "You must specify a target (an ID) and at least one character ID. Consult /charids for the character IDs." ) elif len(arg) == 1: - raise ArgumentError( - "You must specific at least one character ID. Consult /charids for the character IDs." - ) + raise ArgumentError("You must specific at least one character ID. Consult /charids for the character IDs.") args = arg.split() try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) except Exception: - raise ArgumentError( - "You must specify a valid target! Make sure it is a valid ID." - ) + raise ArgumentError("You must specify a valid target! Make sure it is a valid ID.") if targets: for c in targets: log_msg = "" @@ -399,22 +379,15 @@ def ooc_cmd_charcurse(client, arg): try: cid = int(raw_cid) c.charcurse.append(cid) - part_msg += " " + \ - str(client.area.area_manager.char_list[cid]) + "," - log_msg += " " + \ - str(client.area.area_manager.char_list[cid]) + "," + part_msg += " " + str(client.area.area_manager.char_list[cid]) + "," + log_msg += " " + str(client.area.area_manager.char_list[cid]) + "," except Exception: - raise ArgumentError( - "" + str(raw_cid) + - " does not look like a valid character ID." - ) + raise ArgumentError("" + str(raw_cid) + " does not look like a valid character ID.") part_msg = part_msg[:-1] part_msg += "." log_msg = log_msg[:-1] c.char_select() - database.log_area( - "charcurse", client, client.area, target=c, message=log_msg - ) + database.log_area("charcurse", client, client.area, target=c, message=log_msg) client.send_ooc("Charcursed" + part_msg) else: client.send_ooc("No targets found.") @@ -430,13 +403,9 @@ def ooc_cmd_uncharcurse(client, arg): raise ArgumentError("You must specify a target (an ID).") args = arg.split() try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) except Exception: - raise ArgumentError( - "You must specify a valid target! Make sure it is a valid ID." - ) + raise ArgumentError("You must specify a valid target! Make sure it is a valid ID.") if targets: for c in targets: if len(c.charcurse) > 0: @@ -489,9 +458,7 @@ def ooc_cmd_blind(client, arg): targets = [] ids = [int(s) for s in arg.split(" ")] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: @@ -522,9 +489,7 @@ def ooc_cmd_unblind(client, arg): targets = [] ids = [int(s) for s in arg.split(" ")] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: @@ -549,42 +514,28 @@ def ooc_cmd_player_move_delay(client, arg): """ args = shlex.split(arg) try: - if len(args) > 0 and ( - client.is_mod or client in client.area.area_manager.owners - ): + if len(args) > 0 and (client.is_mod or client in client.area.area_manager.owners): # Try to find by char name first - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, args[0] - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, args[0]) # If that doesn't work, find by client ID if len(targets) == 0 and args[0].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]) - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0])) # If that doesn't work, find by OOC Name if len(targets) == 0: - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, args[0] - ) + targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, args[0]) c = targets[0] if len(args) > 1: - move_delay = min( - 1800, max(-1800, int(args[1])) - ) # Move delay is limited between -1800 and 1800 + move_delay = min(1800, max(-1800, int(args[1]))) # Move delay is limited between -1800 and 1800 c.move_delay = move_delay - client.send_ooc( - f"Set move delay for {c.char_name} to {c.move_delay}.") + client.send_ooc(f"Set move delay for {c.char_name} to {c.move_delay}.") else: - client.send_ooc( - f"Move delay for {c.char_name} is {c.move_delay}.") + client.send_ooc(f"Move delay for {c.char_name} is {c.move_delay}.") else: client.send_ooc(f"Your current move delay is {client.move_delay}.") except ValueError: raise ArgumentError("Delay must be an integer between -1800 and 1800.") except IndexError: - raise ArgumentError( - "Target client not found. Use /player_move_delay <id> [delay]." - ) + raise ArgumentError("Target client not found. Use /player_move_delay <id> [delay].") except (AreaError, ClientError): raise @@ -600,32 +551,23 @@ def ooc_cmd_player_hide(client, arg): raise ArgumentError("You must specify a target.") args = arg.split() if args[0] == "*": - targets = [ - c for c in client.area.clients if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] else: try: targets = [] ids = [int(s) for s in args] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: - raise ArgumentError( - "You must specify a target. Use /player_unhide <id> [id(s)]." - ) + raise ArgumentError("You must specify a target. Use /player_unhide <id> [id(s)].") if targets: for c in targets: if c.hidden: - raise ClientError( - f"Client [{c.id}] {c.showname} already hidden!") + raise ClientError(f"Client [{c.id}] {c.showname} already hidden!") c.hide(True) - client.send_ooc( - f"You have hidden [{c.id}] {c.showname} from /getarea and playercounts." - ) + client.send_ooc(f"You have hidden [{c.id}] {c.showname} from /getarea and playercounts.") else: client.send_ooc("No targets found.") @@ -641,32 +583,23 @@ def ooc_cmd_player_unhide(client, arg): raise ArgumentError("You must specify a target.") args = arg.split() if args[0] == "*": - targets = [ - c for c in client.area.clients if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] else: try: targets = [] ids = [int(s) for s in args] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: - raise ArgumentError( - "You must specify a target. Use /player_unhide <id> [id(s)]." - ) + raise ArgumentError("You must specify a target. Use /player_unhide <id> [id(s)].") if targets: for c in targets: if not c.hidden: - raise ClientError( - f"Client [{c.id}] {c.showname} already revealed!") + raise ClientError(f"Client [{c.id}] {c.showname} already revealed!") c.hide(False) - client.send_ooc( - f"You have revealed [{c.id}] {c.showname} for /getarea and playercounts." - ) + client.send_ooc(f"You have revealed [{c.id}] {c.showname} for /getarea and playercounts.") else: client.send_ooc("No targets found.") @@ -677,9 +610,7 @@ def ooc_cmd_hide(client, arg): Usage: /hide <evi_name/id> """ if arg == "": - raise ArgumentError( - "Use /hide <evi_name/id> to hide in evidence, or /unhide to stop hiding." - ) + raise ArgumentError("Use /hide <evi_name/id> to hide in evidence, or /unhide to stop hiding.") try: if arg.isnumeric(): arg = str(int(arg) - 1) @@ -708,25 +639,19 @@ def ooc_cmd_sneak(client, arg): """ if not arg: if client.sneaking: - raise ClientError( - "You are already sneaking! Use /unsneak to stop sneaking.") + raise ClientError("You are already sneaking! Use /unsneak to stop sneaking.") client.sneak(True) else: args = shlex.split(arg) try: if args[0].isnumeric(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) else: - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, args[0], False - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, args[0], False) for x in targets: force_sneak(client, x) except Exception as ex: - raise ArgumentError( - f"Error encountered: {ex}. Use /sneak [id]") + raise ArgumentError(f"Error encountered: {ex}. Use /sneak [id]") def ooc_cmd_unsneak(client, arg): @@ -737,25 +662,19 @@ def ooc_cmd_unsneak(client, arg): """ if not arg: if not client.sneaking: - raise ClientError( - "You are not sneaking! Use /sneak to start sneaking.") + raise ClientError("You are not sneaking! Use /sneak to start sneaking.") client.sneak(False) else: args = shlex.split(arg) try: if args[0].isnumeric(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) else: - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, args[0], False - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, args[0], False) for x in targets: force_unsneak(client, x) except Exception as ex: - raise ArgumentError( - f"Error encountered: {ex}. Use /unsneak [id]") + raise ArgumentError(f"Error encountered: {ex}. Use /unsneak [id]") @mod_only(area_owners=True) @@ -780,9 +699,7 @@ def ooc_cmd_freeze(client, arg): targets = [] ids = [int(s) for s in arg.split(" ")] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: @@ -794,9 +711,7 @@ def ooc_cmd_freeze(client, arg): client.send_ooc(f"Client [{c.id}] {c.name} already frozen! Use /unfreeze {c.id} to undo.") continue c.freeze(True) - client.send_ooc( - f"You have frozen [{c.id}] {c.name} from being able to move between areas." - ) + client.send_ooc(f"You have frozen [{c.id}] {c.name} from being able to move between areas.") else: raise ArgumentError("No targets found.") @@ -813,9 +728,7 @@ def ooc_cmd_unfreeze(client, arg): targets = [] ids = [int(s) for s in arg.split(" ")] for targ_id in ids: - c = client.server.client_manager.get_targets( - client, TargetType.ID, targ_id, False - ) + c = client.server.client_manager.get_targets(client, TargetType.ID, targ_id, False) if c: targets = targets + c except Exception: @@ -861,9 +774,7 @@ def ooc_cmd_unlisten_pos(client, arg): if client.listen_pos is None: raise ClientError("You are not listening to any pos at the moment!") client.listen_pos = None - client.send_ooc( - "You re no longer listening to any pos (All IC messages will appear as normal)." - ) + client.send_ooc("You re no longer listening to any pos (All IC messages will appear as normal).") @mod_only(hub_owners=True) @@ -909,14 +820,10 @@ def mod_keys(client, arg, mod=0): """ args = arg.split() if len(args) <= 1 and mod != 0: - raise ArgumentError( - "Please provide the key(s) to set. Keys must be a number 5 or a link eg. 1-5." - ) + raise ArgumentError("Please provide the key(s) to set. Keys must be a number 5 or a link eg. 1-5.") try: if args[0].isnumeric(): - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) if target: target = target[0].char_id else: @@ -925,7 +832,7 @@ def mod_keys(client, arg, mod=0): else: try: target = client.area.area_manager.get_char_id_by_name(arg) - except (ServerError): + except ServerError: raise if len(args) > 1: @@ -939,16 +846,13 @@ def mod_keys(client, arg, mod=0): # make sure all the keys are integers key = int(key) if mod in (1, 2): - keys = client.area.area_manager.get_character_data( - target, "keys", []) + keys = client.area.area_manager.get_character_data(target, "keys", []) if a in keys and mod == 2: keys.remove(a) elif a not in keys: keys.append(a) client.area.area_manager.set_character_data(target, "keys", keys) - client.send_ooc( - f"Character folder {client.area.area_manager.char_list[target]}'s keys are updated: {keys}" - ) + client.send_ooc(f"Character folder {client.area.area_manager.char_list[target]}'s keys are updated: {keys}") except ValueError: raise ArgumentError("Keys must be a number like 5 or a link eg. 1-5.") except (AreaError, ClientError): @@ -986,9 +890,7 @@ def ooc_cmd_keys_remove(client, arg): Usage: /keys_remove <char> [key(s)] """ if not arg: - raise ArgumentError( - "Usage: /keys_remove <char> [area id(s)]. Removes the selected 'keys' from the user." - ) + raise ArgumentError("Usage: /keys_remove <char> [area id(s)]. Removes the selected 'keys' from the user.") mod_keys(client, arg, 2) @@ -1009,9 +911,7 @@ def ooc_cmd_keys(client, arg): if len(args) == 1: try: if args[0].isnumeric(): - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) if target: target = target[0].char_id else: @@ -1020,13 +920,10 @@ def ooc_cmd_keys(client, arg): else: try: target = client.area.area_manager.get_char_id_by_name(arg) - except (ServerError): + except ServerError: raise - keys = client.area.area_manager.get_character_data( - target, "keys", []) - client.send_ooc( - f"{client.area.area_manager.char_list[target]} current keys are {keys}" - ) + keys = client.area.area_manager.get_character_data(target, "keys", []) + client.send_ooc(f"{client.area.area_manager.char_list[target]} current keys are {keys}") except Exception: raise ArgumentError("Target not found.") else: @@ -1041,9 +938,7 @@ def ooc_cmd_kms(client, arg): """ if arg != "": raise ArgumentError("This command takes no arguments!") - for target in client.server.client_manager.get_multiclients( - client.ipid, client.hdid - ): + for target in client.server.client_manager.get_multiclients(client.ipid, client.hdid): if target != client: target.disconnect() client.send_ooc("Kicked other instances of client.") @@ -1072,15 +967,11 @@ def ooc_cmd_chardesc(client, arg): if arg.isnumeric(): try: - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), True - )[0].char_id - desc = client.area.area_manager.get_character_data( - target, "desc", "") + target = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), True)[0].char_id + desc = client.area.area_manager.get_character_data(target, "desc", "") target = client.area.area_manager.char_list[target] client.send_ooc(f"📜{target} Description: {desc}") - database.log_area("chardesc.request", client, - client.area, message=target) + database.log_area("chardesc.request", client, client.area, message=target) except Exception: raise ArgumentError("Target not found.") else: @@ -1093,9 +984,7 @@ def ooc_cmd_chardesc(client, arg): desc = arg[:128] if len(arg) > len(desc): desc += f"... Use /chardesc {client.id} to read the rest." - client.area.broadcast_ooc( - f"{client.showname} changed their character description to: {desc}." - ) + client.area.broadcast_ooc(f"{client.showname} changed their character description to: {desc}.") client.area.broadcast_player_list() database.log_area("chardesc.change", client, client.area, message=arg) @@ -1107,15 +996,11 @@ def ooc_cmd_chardesc_clear(client, arg): """ client.area.area_manager.set_character_data(client.char_id, "desc", "") if not client.hidden and not client.sneaking: - client.area.broadcast_ooc( - f"{client.showname} cleared their character description." - ) + client.area.broadcast_ooc(f"{client.showname} cleared their character description.") client.area.broadcast_player_list() else: client.send_ooc("You cleared your character description.") - database.log_area( - "chardesc.clear", client, client.area - ) + database.log_area("chardesc.clear", client, client.area) @mod_only(hub_owners=True) @@ -1126,13 +1011,10 @@ def ooc_cmd_chardesc_set(client, arg): """ args = arg.split(" ") if len(args) < 1: - raise ArgumentError( - "Not enough arguments. Usage: /chardesc_set <id> [desc]") + raise ArgumentError("Not enough arguments. Usage: /chardesc_set <id> [desc]") try: if args[0].isnumeric(): - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) if target: target = target[0].char_id else: @@ -1141,7 +1023,7 @@ def ooc_cmd_chardesc_set(client, arg): else: try: target = client.area.area_manager.get_char_id_by_name(arg) - except (ServerError): + except ServerError: raise desc = "" if len(args) > 1: @@ -1149,9 +1031,7 @@ def ooc_cmd_chardesc_set(client, arg): client.area.area_manager.set_character_data(target, "desc", desc) target = client.area.area_manager.char_list[target] client.send_ooc(f"📜{target} Description: {desc}") - database.log_area( - "chardesc.set", client, client.area, message=f"{target}: {desc}" - ) + database.log_area("chardesc.set", client, client.area, message=f"{target}: {desc}") if not client.hidden and not client.sneaking: client.area.broadcast_player_list() except Exception: @@ -1166,9 +1046,7 @@ def ooc_cmd_chardesc_get(client, arg): """ try: if arg.isnumeric(): - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) if target: target = target[0].char_id else: @@ -1177,14 +1055,12 @@ def ooc_cmd_chardesc_get(client, arg): else: try: target = client.area.area_manager.get_char_id_by_name(arg) - except (ServerError): + except ServerError: raise desc = client.area.area_manager.get_character_data(target, "desc", "") target = client.area.area_manager.char_list[target] client.send_ooc(f"📜{target} Description: {desc}") - database.log_area( - "chardesc.get", client, client.area, message=f"{target}: {desc}" - ) + database.log_area("chardesc.get", client, client.area, message=f"{target}: {desc}") except Exception: raise ArgumentError("Target not found.") @@ -1197,9 +1073,7 @@ def ooc_cmd_narrate(client, arg): Usage: /narrate [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.narrator = True @@ -1211,9 +1085,7 @@ def ooc_cmd_narrate(client, arg): client.narrator = not client.narrator if client.blankpost is True: client.blankpost = False - client.send_ooc( - "You cannot be a narrator and blankposting at the same time. Blankposting disabled!" - ) + client.send_ooc("You cannot be a narrator and blankposting at the same time. Blankposting disabled!") stat = "no longer be narrating" if client.narrator: stat = "be narrating now" @@ -1227,9 +1099,7 @@ def ooc_cmd_blankpost(client, arg): Usage: /blankpost [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.blankpost = True @@ -1241,9 +1111,7 @@ def ooc_cmd_blankpost(client, arg): client.blankpost = not client.blankpost if client.narrator is True: client.narrator = False - client.send_ooc( - "You cannot be a narrator and blankposting at the same time. Narrating disabled!" - ) + client.send_ooc("You cannot be a narrator and blankposting at the same time. Narrating disabled!") stat = "no longer be blankposting" if client.blankpost: stat = "be blankposting now" @@ -1258,9 +1126,7 @@ def ooc_cmd_firstperson(client, arg): Usage: /firstperson [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.firstperson = True @@ -1272,9 +1138,7 @@ def ooc_cmd_firstperson(client, arg): client.firstperson = not client.firstperson if client.narrator is True: client.narrator = False - client.send_ooc( - "You cannot be a narrator and firstperson at the same time. Narrating disabled!" - ) + client.send_ooc("You cannot be a narrator and firstperson at the same time. Narrating disabled!") stat = "no longer be firstperson" if client.firstperson: stat = "be firstperson now" @@ -1298,8 +1162,7 @@ def ooc_cmd_showname(client, arg): client.send_ooc("Your IC showname is way too long!") return if not client.is_mod and arg.lstrip().lower().startswith("[m"): - client.send_ooc( - "Nice try! You may not spoof [M] tag in your showname.") + client.send_ooc("Nice try! You may not spoof [M] tag in your showname.") return client.used_showname_command = True client.showname = arg @@ -1332,21 +1195,17 @@ def ooc_cmd_webfiles(client, arg): try: if args[0] == "*": - targets = [ - c - for c in client.area.clients - if c != client and c != client.area.owners - ] + targets = [c for c in client.area.clients if c != client and c != client.area.owners] else: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False) except ValueError: raise ArgumentError("Target ID must be a number or *.") try: for c in targets: - client.send_ooc(f"To download the files, visit https://attorneyonline.github.io/webDownloader/index.html?char={c.iniswap}") + client.send_ooc( + f"To download the files, visit https://attorneyonline.github.io/webDownloader/index.html?char={c.iniswap}" + ) except Exception: raise ClientError("You must specify a target. Use /webfiles <id>") @@ -1384,9 +1243,7 @@ def ooc_cmd_triple_pair(client, arg): return if arg.isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), True) if len(targets) > 0: client.third_charid = targets[0].char_id else: @@ -1397,14 +1254,17 @@ def ooc_cmd_triple_pair(client, arg): char = client.third_charid if client.third_charid in range(0, len(client.area.area_manager.char_list)): char = client.area.area_manager.char_list[client.third_charid] - client.send_ooc(f"Successfully paired with '{char}'! Ask them to pair with you back, and show up on the same /pos for it to work.") + client.send_ooc( + f"Successfully paired with '{char}'! Ask them to pair with you back, and show up on the same /pos for it to work." + ) + def get_latest_area(client, char_id: int): char_folder = None if char_id in range(0, len(client.area.area_manager.char_list)): char_folder = client.area.area_manager.char_list[char_id] if char_folder is None: - print(char_folder, ' ', char_id) + print(char_folder, " ", char_id) client.send_ooc("Can't get latest area when spectating!") return None latest_area_id = client.area.area_manager.get_character_data(char_id, "latest_area", None) @@ -1419,6 +1279,7 @@ def get_latest_area(client, char_id: int): return None return target_area + @mod_only(hub_owners=True) def ooc_cmd_get_latest_area(client, arg): """ @@ -1430,22 +1291,23 @@ def ooc_cmd_get_latest_area(client, arg): if len(arg) == 0: target_charid = client.char_id elif arg.isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), True) if len(targets) > 0: target_charid = targets[0].char_id else: - arg = arg.replace("\"", "").lower() + arg = arg.replace('"', "").lower() for i in range(0, len(client.area.area_manager.char_list)): if arg == client.area.area_manager.char_list[i].lower(): target_charid = i area = get_latest_area(client, target_charid) if area: - client.send_ooc(f"{client.area.area_manager.char_list[target_charid]} latest occupied area is [{area.id}] {area.name}.") - else: + client.send_ooc( + f"{client.area.area_manager.char_list[target_charid]} latest occupied area is [{area.id}] {area.name}." + ) + else: client.send_ooc("Area not found!") + @mod_only(hub_owners=True) def ooc_cmd_kick_to_latest_area(client, arg): """ @@ -1458,13 +1320,11 @@ def ooc_cmd_kick_to_latest_area(client, arg): target_charid = client.char_id targets = [client] elif arg.isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), True) if len(targets) > 0: target_charid = targets[0].char_id else: - arg = arg.replace("\"", "").lower() + arg = arg.replace('"', "").lower() for i in range(0, len(client.area.area_manager.char_list)): if arg == client.area.area_manager.char_list[i].lower(): target_charid = i @@ -1477,12 +1337,8 @@ def ooc_cmd_kick_to_latest_area(client, arg): for target in targets: old_area = target.area target.set_area(area) - target.send_ooc( - f"You were kicked from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}." - ) - database.log_area( - "kick_to_latest_area", client, client.area, target=target, message=area.id - ) + target.send_ooc(f"You were kicked from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}.") + database.log_area("kick_to_latest_area", client, client.area, target=target, message=area.id) client.area.invite_list.discard(target.id) client.send_ooc( f"Kicked [{target.id}] {target.showname} from [{old_area.id}] {old_area.name} to [{area.id}] {area.name}." @@ -1492,6 +1348,7 @@ def ooc_cmd_kick_to_latest_area(client, arg): except ClientError: raise + @mod_only(hub_owners=True) def ooc_cmd_set_latest_area(client, arg): """ @@ -1501,16 +1358,12 @@ def ooc_cmd_set_latest_area(client, arg): """ args = shlex.split(arg) if len(args) == 0: - raise ArgumentError( - "Not enough args. Usage: /set_latest_area <cid|charname> [area_id]" - ) + raise ArgumentError("Not enough args. Usage: /set_latest_area <cid|charname> [area_id]") target_charid = -1 if len(args) == 1: target_charid = client.char_id elif args[0].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[1]), True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[1]), True) if len(targets) > 0: target_charid = targets[0].char_id else: @@ -1532,4 +1385,6 @@ def ooc_cmd_set_latest_area(client, arg): target_area = client.area.area_manager.get_area_by_id(to_area) client.send_ooc(f"Successfuly set {char_folder} latest occupied area to [{target_area.id}] {target_area.name}.") except Exception: - client.send_ooc(f"Warning: setting {char_folder} latest occupied area to an invalid area for current hub. Area ID: [{to_area}].") \ No newline at end of file + client.send_ooc( + f"Warning: setting {char_folder} latest occupied area to an invalid area for current hub. Area ID: [{to_area}]." + ) diff --git a/server/commands/fun.py b/server/commands/fun.py index 94f3218f..2aa27ad1 100644 --- a/server/commands/fun.py +++ b/server/commands/fun.py @@ -22,9 +22,7 @@ def ooc_cmd_disemvowel(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must specify a target. Use /disemvowel <id>.") if targets: @@ -45,12 +43,9 @@ def ooc_cmd_undisemvowel(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: - raise ArgumentError( - "You must specify a target. Use /undisemvowel <id>.") + raise ArgumentError("You must specify a target. Use /undisemvowel <id>.") if targets: for c in targets: database.log_area("undisemvowel", client, client.area, target=c) @@ -69,9 +64,7 @@ def ooc_cmd_shake(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must specify a target. Use /shake <id>.") if targets: @@ -92,9 +85,7 @@ def ooc_cmd_unshake(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must specify a target. Use /unshake <id>.") if targets: @@ -113,6 +104,4 @@ def ooc_cmd_rainbow(client, arg): """ client.rainbow = not client.rainbow toggle = "now" if client.rainbow else "no longer" - client.send_ooc( - f"You will {toggle} have rainbowtext." - ) + client.send_ooc(f"You will {toggle} have rainbowtext.") diff --git a/server/commands/hubs.py b/server/commands/hubs.py index 7e317681..5a9da146 100644 --- a/server/commands/hubs.py +++ b/server/commands/hubs.py @@ -66,12 +66,7 @@ def ooc_cmd_hub(client, arg): h = arg.split(" ")[0] hid = h.strip("[]") if ( - ( - h.startswith("[") - and h.endswith("]") - and hid.isdigit() - and hub.id == int(hid) - ) + (h.startswith("[") and h.endswith("]") and hid.isdigit() and hub.id == int(hid)) or hub.name.lower() == arg.lower() or hub.abbreviation == arg or (arg.isdigit() and hub.id == int(arg)) @@ -107,8 +102,7 @@ def ooc_cmd_save_hub(client, arg): args = shlex.split(arg) if not client.is_mod: if arg == "": - raise ArgumentError( - "You must be authorized to save the default hub!") + raise ArgumentError("You must be authorized to save the default hub!") if len(args[0]) < 3: raise ArgumentError("Filename must be at least 3 symbols long!") try: @@ -118,14 +112,9 @@ def ooc_cmd_save_hub(client, arg): path = "storage/hubs/read_only" else: path = "storage/hubs" - num_files = len( - [f for f in os.listdir(path) if os.path.isfile( - os.path.join(path, f))] - ) + num_files = len([f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]) if num_files >= 1000: # yikes - raise AreaError( - "Server storage full! Please contact the server host to resolve this issue." - ) + raise AreaError("Server storage full! Please contact the server host to resolve this issue.") try: if os.path.isfile(f"storage/hubs/read_only/{name}.yaml"): raise ArgumentError(f"Hub {name} already exists and it is read-only!") @@ -156,9 +145,7 @@ def ooc_cmd_save_hub(client, arg): client.send_ooc(f"Saving as {name}...") else: client.server.hub_manager.save("config/areas_new.yaml") - client.send_ooc( - "Saving all Hubs to areas_new.yaml. Contact the server owner to apply the changes." - ) + client.send_ooc("Saving all Hubs to areas_new.yaml. Contact the server owner to apply the changes.") except AreaError: raise @@ -187,8 +174,7 @@ def ooc_cmd_load_hub(client, arg): client.area.area_manager.send_arup_status() client.area.area_manager.send_arup_cms() client.area.area_manager.send_arup_lock() - client.server.client_manager.refresh_music( - client.area.area_manager.clients) + client.server.client_manager.refresh_music(client.area.area_manager.clients) client.send_ooc("Success, sending ARUP and refreshing music...") else: client.server.hub_manager.load(hub_id=client.area.area_manager.id) @@ -226,8 +212,7 @@ def ooc_cmd_overlay_hub(client, arg): client.area.area_manager.send_arup_status() client.area.area_manager.send_arup_cms() client.area.area_manager.send_arup_lock() - client.server.client_manager.refresh_music( - client.area.area_manager.clients) + client.server.client_manager.refresh_music(client.area.area_manager.clients) client.send_ooc("Success, sending ARUP and refreshing music...") else: client.server.hub_manager.load() @@ -288,8 +273,7 @@ def ooc_cmd_clear_hub(client, arg): try: client.server.hub_manager.load(hub_id=client.area.area_manager.id) client.area.area_manager.broadcast_ooc("Hub clearing initiated...") - client.server.client_manager.refresh_music( - client.area.area_manager.clients) + client.server.client_manager.refresh_music(client.area.area_manager.clients) client.send_ooc("Success, sending ARUP and refreshing music...") except AreaError: raise @@ -303,12 +287,9 @@ def ooc_cmd_rename_hub(client, arg): """ if arg != "": client.area.area_manager.name = dezalgo(arg)[:64] - client.send_ooc( - f"Renamed hub [{client.area.area_manager.id}] to {client.area.area_manager.name}." - ) + client.send_ooc(f"Renamed hub [{client.area.area_manager.id}] to {client.area.area_manager.name}.") else: - raise ArgumentError( - "Invalid number of arguments. Use /rename_hub <name>.") + raise ArgumentError("Invalid number of arguments. Use /rename_hub <name>.") @mod_only(hub_owners=True) @@ -354,8 +335,7 @@ def ooc_cmd_area_remove(client, arg): except (AreaError, ClientError): raise else: - raise ArgumentError( - "Invalid number of arguments. Use /area_remove <aid>.") + raise ArgumentError("Invalid number of arguments. Use /area_remove <aid>.") @mod_only(hub_owners=True) @@ -380,8 +360,7 @@ def ooc_cmd_area_duplicate(client, arg): except (AreaError, ClientError): raise else: - raise ArgumentError( - "Invalid number of arguments. Use /area_duplicate <aid>.") + raise ArgumentError("Invalid number of arguments. Use /area_duplicate <aid>.") @mod_only(area_owners=True) @@ -396,17 +375,13 @@ def ooc_cmd_area_rename(client, arg): args = arg.split(maxsplit=1) if len(args) <= 0: - raise ArgumentError( - "Invalid number of arguments. Use /area_rename [aid] <name>." - ) + raise ArgumentError("Invalid number of arguments. Use /area_rename [aid] <name>.") # Test if we want to target an area if args[0].isnumeric(): if len(args) == 1: # Can't set the area name to just a number only - raise ArgumentError( - "Only Area ID was provided with no name. Use /area_rename [aid] <name>." - ) + raise ArgumentError("Only Area ID was provided with no name. Use /area_rename [aid] <name>.") try: area = client.area.area_manager.get_area_by_id(int(args[0])) name = args[1] @@ -439,8 +414,7 @@ def ooc_cmd_area_swap(client, arg): area2 = client.area.area_manager.get_area_by_id(int(args[1])) client.area.area_manager.swap_area(area1, area2, True) client.area.area_manager.broadcast_area_list() - client.send_ooc( - f"Area {area1.name} has been swapped with Area {area2.name}!") + client.send_ooc(f"Area {area1.name} has been swapped with Area {area2.name}!") except ValueError: raise ArgumentError("Area IDs must be a number.") except (AreaError, ClientError): @@ -461,8 +435,7 @@ def ooc_cmd_area_switch(client, arg): area2 = client.area.area_manager.get_area_by_id(int(args[1])) client.area.area_manager.swap_area(area1, area2, False) client.area.area_manager.broadcast_area_list() - client.send_ooc( - f"Area {area1.name} has been switched with Area {area2.name}!") + client.send_ooc(f"Area {area1.name} has been switched with Area {area2.name}!") except ValueError: raise ArgumentError("Area IDs must be a number.") except (AreaError, ClientError): @@ -516,20 +489,14 @@ def ooc_cmd_area_pref(client, arg): args = arg.split() if len(args) > 2: - raise ArgumentError( - "Usage: /area_pref | /area_pref <pref> | /area_pref <pref> <on|off>" - ) + raise ArgumentError("Usage: /area_pref | /area_pref <pref> | /area_pref <pref> <on|off>") try: cmd = args[0].lower() attri = getattr(client.area, cmd) if type(attri) is not bool: raise ArgumentError("Preference is not a boolean.") - if ( - not client.is_mod - and client not in client.area.area_manager.owners - and cmd not in cm_allowed - ): + if not client.is_mod and client not in client.area.area_manager.owners and cmd not in cm_allowed: raise ClientError("You need to be a GM to modify this preference.") tog = not attri if len(args) > 1: @@ -563,16 +530,11 @@ def ooc_cmd_area_move_delay(client, arg): args = arg.split() try: if len(args) > 0: - move_delay = min( - 1800, max(-1800, int(args[0])) - ) # Move delay is limited between -1800 and 1800 + move_delay = min(1800, max(-1800, int(args[0]))) # Move delay is limited between -1800 and 1800 client.area.move_delay = move_delay - client.send_ooc( - f"Set {client.area.name} movement delay to {move_delay}.") + client.send_ooc(f"Set {client.area.name} movement delay to {move_delay}.") else: - client.send_ooc( - f"Current move delay for {client.area.name} is {client.area.move_delay}." - ) + client.send_ooc(f"Current move delay for {client.area.name} is {client.area.move_delay}.") except ValueError: raise ArgumentError("Delay must be an integer between -1800 and 1800.") except (AreaError, ClientError): @@ -589,13 +551,9 @@ def ooc_cmd_hub_move_delay(client, arg): args = arg.split() try: if len(args) > 0: - move_delay = min( - 1800, max(-1800, int(args[0])) - ) # Move delay is limited between -1800 and 1800 + move_delay = min(1800, max(-1800, int(args[0]))) # Move delay is limited between -1800 and 1800 client.area.area_manager.move_delay = move_delay - client.send_ooc( - f"Set {client.area.area_manager.name} movement delay to {move_delay}." - ) + client.send_ooc(f"Set {client.area.area_manager.name} movement delay to {move_delay}.") else: client.send_ooc( f"Current move delay for {client.area.area_manager.name} is {client.area.area_manager.move_delay}." @@ -617,11 +575,8 @@ def ooc_cmd_toggle_replace_music(client, arg): boolean = arg in ["true", "on", "1"] client.area.area_manager.replace_music = boolean toggle = "now" if client.area.area_manager.replace_music else "no longer" - client.server.client_manager.refresh_music( - client.area.area_manager.clients) - client.area.area_manager.broadcast_ooc( - f"Hub music list will {toggle} replace server music list." - ) + client.server.client_manager.refresh_music(client.area.area_manager.clients) + client.area.area_manager.broadcast_ooc(f"Hub music list will {toggle} replace server music list.") @mod_only(hub_owners=True) @@ -635,9 +590,7 @@ def ooc_cmd_toggle_passing_ic(client, arg): boolean = arg in ["true", "on", "1"] client.area.area_manager.passing_msg = boolean toggle = "enabled" if client.area.area_manager.passing_msg else "disabled" - client.area.area_manager.broadcast_ooc( - f"IC area passing messages are now {toggle} for this hub." - ) + client.area.area_manager.broadcast_ooc(f"IC area passing messages are now {toggle} for this hub.") @mod_only(hub_owners=True) @@ -648,15 +601,11 @@ def ooc_cmd_arup_enable(client, arg): Usage: /arup_enable """ if client.area.area_manager.arup_enabled: - raise ClientError( - "ARUP system is already enabled! Use /arup_disable to disable it." - ) + raise ClientError("ARUP system is already enabled! Use /arup_disable to disable it.") client.area.area_manager.arup_enabled = True - client.area.area_manager.send_command( - "FL", client.server.supported_features) + client.area.area_manager.send_command("FL", client.server.supported_features) client.area.area_manager.broadcast_area_list(refresh=True) - client.area.area_manager.broadcast_ooc( - "ARUP system has been enabled for this hub.") + client.area.area_manager.broadcast_ooc("ARUP system has been enabled for this hub.") @mod_only(hub_owners=True) @@ -666,17 +615,13 @@ def ooc_cmd_arup_disable(client, arg): Usage: /arup_disable """ if not client.area.area_manager.arup_enabled: - raise ClientError( - "ARUP system is already disabled! Use /arup_enable to enable it." - ) + raise ClientError("ARUP system is already disabled! Use /arup_enable to enable it.") client.area.area_manager.arup_enabled = False preflist = client.server.supported_features.copy() preflist.remove("arup") client.area.area_manager.send_command("FL", *preflist) client.area.area_manager.broadcast_area_list(refresh=True) - client.area.area_manager.broadcast_ooc( - "ARUP system has been disabled for this hub." - ) + client.area.area_manager.broadcast_ooc("ARUP system has been disabled for this hub.") @mod_only(hub_owners=True) @@ -690,9 +635,7 @@ def ooc_cmd_toggle_getareas(client, arg): boolean = arg in ["true", "on", "1"] client.area.area_manager.can_getareas = boolean toggle = "enabled" if client.area.area_manager.can_getareas else "disabled" - client.area.area_manager.broadcast_ooc( - f"Use of /getareas has been {toggle} for this hub." - ) + client.area.area_manager.broadcast_ooc(f"Use of /getareas has been {toggle} for this hub.") @mod_only(hub_owners=True) @@ -706,9 +649,7 @@ def ooc_cmd_toggle_spectate(client, arg): boolean = arg in ["true", "on", "1"] client.area.area_manager.can_spectate = boolean toggle = "enabled" if client.area.area_manager.can_spectate else "disabled" - client.area.area_manager.broadcast_ooc( - f"Spectating has been {toggle} for this hub." - ) + client.area.area_manager.broadcast_ooc(f"Spectating has been {toggle} for this hub.") @mod_only(hub_owners=True) @@ -718,14 +659,10 @@ def ooc_cmd_hide_clients(client, arg): Usage: /hide_clients """ if client.area.area_manager.hide_clients: - raise ClientError( - "Client playercounts already hidden! Use /unhide_clients to unhide." - ) + raise ClientError("Client playercounts already hidden! Use /unhide_clients to unhide.") client.area.area_manager.hide_clients = True client.area.area_manager.broadcast_area_list() - client.area.area_manager.broadcast_ooc( - "Client playercounts are now hidden for this hub." - ) + client.area.area_manager.broadcast_ooc("Client playercounts are now hidden for this hub.") @mod_only(hub_owners=True) @@ -735,14 +672,10 @@ def ooc_cmd_unhide_clients(client, arg): Usage: /unhide_clients """ if not client.area.area_manager.hide_clients: - raise ClientError( - "Client playercounts already revealed! Use /hide_clients to hide." - ) + raise ClientError("Client playercounts already revealed! Use /hide_clients to hide.") client.area.area_manager.hide_clients = False client.area.area_manager.broadcast_area_list() - client.area.area_manager.broadcast_ooc( - "Client playercounts are no longer hidden for this hub." - ) + client.area.area_manager.broadcast_ooc("Client playercounts are no longer hidden for this hub.") @mod_only(hub_owners=True) @@ -753,37 +686,29 @@ def ooc_cmd_force_follow(client, arg): """ arg = arg.split() if len(arg) == 0: - raise ArgumentError( - "You must specify a victim. Usage: /force_follow <victim_id> [target_id]") + raise ArgumentError("You must specify a victim. Usage: /force_follow <victim_id> [target_id]") try: - victims = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg[0]), False - ) + victims = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[0]), False) except Exception: - raise ArgumentError( - "You must specify a victim. Usage: /force_follow <victim_id> [target_id]") + raise ArgumentError("You must specify a victim. Usage: /force_follow <victim_id> [target_id]") target = client if len(arg) >= 2: try: - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg[1]), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[1]), False) except Exception: - raise ArgumentError( - "Invalid target! Usage: /force_follow <victim_id> [target_id]") + raise ArgumentError("Invalid target! Usage: /force_follow <victim_id> [target_id]") if victims: for c in victims: if client == c: - raise ClientError( - "You are already forced to follow yourself because you are yourself!" - ) + raise ClientError("You are already forced to follow yourself because you are yourself!") c.following = client c.forced_to_follow = True c.send_ooc(f"You've been forced to follow {client.showname}!") if c.area != client.area: c.set_area(client.area) client.send_ooc( - f"Forced {len(victims)} client(s) to follow [{target.id}] {target.showname}. Use /unfollow {target.id} to free them.") + f"Forced {len(victims)} client(s) to follow [{target.id}] {target.showname}. Use /unfollow {target.id} to free them." + ) else: client.send_ooc("No targets found.") @@ -795,9 +720,7 @@ def ooc_cmd_follow(client, arg): """ if len(arg) == 0: try: - client.send_ooc( - f"You are currently following [{client.following.id}] {client.following.showname}." - ) + client.send_ooc(f"You are currently following [{client.following.id}] {client.following.showname}.") except Exception: raise ArgumentError("Not following anybody. Use /follow <id>.") return @@ -807,27 +730,17 @@ def ooc_cmd_follow(client, arg): and client not in client.area.area_manager.owners and client.following is not None ): - raise ClientError( - "You can't change follow targets while being forced to follow!" - ) + raise ClientError("You can't change follow targets while being forced to follow!") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must specify a target. Use /follow <id>.") if targets: c = targets[0] if client == c: raise ClientError("Can't follow yourself!") - if ( - c not in client.area.clients - and not client.is_mod - and client not in client.area.area_manager.owners - ): - raise ClientError( - "You are not a mod/GM - Target must be present in your area!" - ) + if c not in client.area.clients and not client.is_mod and client not in client.area.area_manager.owners: + raise ClientError("You are not a mod/GM - Target must be present in your area!") if client.following and client.following.id == c.id: ooc_cmd_unfollow(client, "") return @@ -849,16 +762,10 @@ def ooc_cmd_unfollow(client, arg): """ allowed = client.is_mod or client in client.area.area_manager.owners if len(arg) == 0: - if ( - client.forced_to_follow - and not allowed - and client.following is not None - ): + if client.forced_to_follow and not allowed and client.following is not None: raise ClientError("You can't escape being forced to follow!") try: - client.send_ooc( - f"You are no longer following [{client.following.id}] {client.following.showname}." - ) + client.send_ooc(f"You are no longer following [{client.following.id}] {client.following.showname}.") client.following = None client.forced_to_follow = False except Exception: @@ -884,27 +791,21 @@ def ooc_cmd_unfollow(client, arg): if client.is_mod or client in client.area.area_manager.owners: try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: - raise ArgumentError( - "You must specify a target. Use /follow_me <id>.") + raise ArgumentError("You must specify a target. Use /follow_me <id>.") if targets: count = 0 for c in targets: if c.forced_to_follow: c.following = None c.forced_to_follow = False - c.send_ooc( - "You've been freed from having to follow someone.") + c.send_ooc("You've been freed from having to follow someone.") count += 1 if count == 0: client.send_ooc("No valid targets found.") else: - client.send_ooc( - f"Freed {count} existing client(s) from having to follow someone." - ) + client.send_ooc(f"Freed {count} existing client(s) from having to follow someone.") else: client.send_ooc("No targets found.") else: @@ -923,9 +824,7 @@ def ooc_cmd_info(client, arg): if not client.is_mod and client not in client.area.area_manager.owners: raise ClientError("You must be a GM of the Hub to do that.") client.area.area_manager.info = arg - client.area.area_manager.broadcast_ooc( - "{} changed the Hub info.".format(client.showname) - ) + client.area.area_manager.broadcast_ooc("{} changed the Hub info.".format(client.showname)) database.log_area("info.change", client, client.area, message=arg) @@ -942,16 +841,13 @@ def ooc_cmd_gm(client, arg): if arg != "": # GM all self clients if arg == "*": - arg = [c.id for c in client.server.client_manager.get_multiclients( - client.ipid, client.hdid)] + arg = [c.id for c in client.server.client_manager.get_multiclients(client.ipid, client.hdid)] # GM the targets else: arg = arg.split(" ") # Client is not a mod and not a GM, meaning they're trying to nominate someone without being /gm first if not client.is_mod and client not in client.area.area_manager.owners: - raise ArgumentError( - "You cannot 'nominate' people to be GMs when you are not one." - ) + raise ArgumentError("You cannot 'nominate' people to be GMs when you are not one.") else: # Self GM arg = [client.id] @@ -959,25 +855,15 @@ def ooc_cmd_gm(client, arg): for id in arg: try: id = int(id) - c = client.server.client_manager.get_targets( - client, TargetType.ID, id, False - )[0] + c = client.server.client_manager.get_targets(client, TargetType.ID, id, False)[0] if c not in client.area.area_manager.clients: - raise ArgumentError( - "You can only 'nominate' people to be GMs when they are in the hub." - ) + raise ArgumentError("You can only 'nominate' people to be GMs when they are in the hub.") elif c in client.area.area_manager.owners: - client.send_ooc( - f"{c.showname} [{c.id}] is already a GM here.") + client.send_ooc(f"{c.showname} [{c.id}] is already a GM here.") else: for mc in c.server.client_manager.get_multiclients(c.ipid, c.hdid): - if ( - mc in mc.area.area_manager.owners - and mc.area.area_manager != c.area.area_manager - ): - raise ClientError( - f"One of {c.showname} [{c.id}]'s clients is already a GM in another hub!" - ) + if mc in mc.area.area_manager.owners and mc.area.area_manager != c.area.area_manager: + raise ClientError(f"One of {c.showname} [{c.id}]'s clients is already a GM in another hub!") client.area.area_manager.add_owner(c) database.log_area("gm.add", client, client.area, target=c) except (ValueError, IndexError): @@ -1002,16 +888,12 @@ def ooc_cmd_ungm(client, arg): for _id in arg: try: _id = int(_id) - c = client.server.client_manager.get_targets( - client, TargetType.ID, _id, False - )[0] + c = client.server.client_manager.get_targets(client, TargetType.ID, _id, False)[0] if c in client.area.area_manager.owners: client.area.area_manager.remove_owner(c) database.log_area("gm.remove", client, client.area, target=c) else: - client.send_ooc( - "You cannot remove someone from GMing when they aren't a GM." - ) + client.send_ooc("You cannot remove someone from GMing when they aren't a GM.") except (ValueError, IndexError): client.send_ooc(f"{id} does not look like a valid ID.") except (ClientError, ArgumentError): @@ -1064,19 +946,16 @@ def ooc_cmd_hpset(client, arg): """ args = shlex.split(arg) if len(args) == 0: - raise ArgumentError( - "You must specify a position and HP. Use /hpset <pos> <amount> [area]") + raise ArgumentError("You must specify a position and HP. Use /hpset <pos> <amount> [area]") elif len(args) == 1: - raise ArgumentError( - "You must specify HP. Use /hpset <pos> <amount> [area]") + raise ArgumentError("You must specify HP. Use /hpset <pos> <amount> [area]") if args[0] == "def": side = 1 elif args[0] == "pro": side = 2 else: - raise ArgumentError( - "Invalid position. Use \"pro\" or \"def\"") + raise ArgumentError('Invalid position. Use "pro" or "def"') area_list = [client.area] if len(args > 2): diff --git a/server/commands/inventory.py b/server/commands/inventory.py index 91c8f0e4..741683fa 100644 --- a/server/commands/inventory.py +++ b/server/commands/inventory.py @@ -39,9 +39,7 @@ def get_inventory(evi_list, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/inventory {arg})" - ) + raise AreaError(f"Target evidence not found! (/inventory {arg})") msg = f"==💼[{i+1}]: '{evidence[0]}==" msg += f"\n🖼️Image: {evidence[2]}" msg += f"\n📃Desc:\n{evidence[1]}" @@ -83,12 +81,8 @@ def ooc_cmd_inventory_drop(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/inventory_drop {arg})" - ) - client.area.evi_list.add_evidence( - client, evidence[0], evidence[1], evidence[2] - ) + raise AreaError(f"Target evidence not found! (/inventory_drop {arg})") + client.area.evi_list.add_evidence(client, evidence[0], evidence[1], evidence[2]) client.remove_inventory_evidence(i) client.area.broadcast_evidence_list() msg = f"You drop '{evidence[0]}' evidence into [{client.id}] {client.area.name}." @@ -101,9 +95,7 @@ def ooc_cmd_inventory_drop(client, arg): def get_inventory_target(client, arg): if arg.isnumeric(): - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + target = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) if target: target = target[0].char_id else: @@ -112,7 +104,7 @@ def get_inventory_target(client, arg): else: try: target = client.area.area_manager.get_char_id_by_name(arg) - except (ServerError): + except ServerError: raise return target @@ -126,19 +118,17 @@ def ooc_cmd_inventory_get(client, arg): try: # Get the user input args = shlex.split(arg) - + target = get_inventory_target(client, args.pop(0)) - + inventory = client.area.area_manager.get_character_data(target, "inventory", "") charname = client.area.area_manager.char_list[target] inventory_list = get_inventory(inventory, " ".join(args)) msg = f"==Evidence in '{charname}' inventory==\n{inventory_list}" client.send_ooc(msg) - database.log_area( - "inventory.get", client, client.area, message=charname - ) + database.log_area("inventory.get", client, client.area, message=charname) except ValueError as ex: - client.send_ooc(f'{ex} (/inventory_get {arg})') + client.send_ooc(f"{ex} (/inventory_get {arg})") return @@ -151,13 +141,14 @@ def ooc_cmd_inventory_add(client, arg): try: # Get the user input args = shlex.split(arg) - + target = get_inventory_target(client, args.pop(0)) max_args = 3 if len(args) > max_args: raise ArgumentError( - f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_add {arg})") + f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_add {arg})" + ) # fill the rest of it with asterisk to fill to max_args args = args + ([""] * (max_args - len(args))) if args[0] == "": @@ -167,7 +158,7 @@ def ooc_cmd_inventory_add(client, arg): if args[2] == "": args[2] = "empty.png" except ValueError as ex: - client.send_ooc(f'{ex} (/inventory_add {arg})') + client.send_ooc(f"{ex} (/inventory_add {arg})") return inventory = client.area.area_manager.get_character_data(target, "inventory", list()) @@ -188,10 +179,11 @@ def ooc_cmd_inventory_remove(client, arg): target = get_inventory_target(client, args.pop(0)) if len(args) > 1: raise ArgumentError( - f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_remove {arg})") + f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_remove {arg})" + ) inventory = client.area.area_manager.get_character_data(target, "inventory", list()) - + arg = " ".join(args) evidence = None for i, evi in enumerate(inventory): @@ -199,13 +191,11 @@ def ooc_cmd_inventory_remove(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/inventory_remove {arg})" - ) + raise AreaError(f"Target evidence not found! (/inventory_remove {arg})") inventory.pop(i) client.area.area_manager.set_character_data(target, "inventory", inventory) except ValueError as ex: - client.send_ooc(f'{ex} (/inventory_remove {arg})') + client.send_ooc(f"{ex} (/inventory_remove {arg})") return @@ -231,11 +221,12 @@ def ooc_cmd_inventory_edit(client, arg): max_args = 3 if len(args) > max_args: raise ArgumentError( - f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_edit {arg})") + f"Too many arguments! Make sure to surround your args in \"\"'s if there's spaces. (/inventory_edit {arg})" + ) # fill the rest of it with asterisk to fill to max_args args = args + (["*"] * (max_args - len(args))) except ValueError as ex: - client.send_ooc(f'{ex} (/inventory_edit {arg})') + client.send_ooc(f"{ex} (/inventory_edit {arg})") return try: @@ -246,17 +237,14 @@ def ooc_cmd_inventory_edit(client, arg): evidence = evi break if evidence is None: - raise AreaError( - f"Target evidence not found! (/inventory_edit {arg})" - ) + raise AreaError(f"Target evidence not found! (/inventory_edit {arg})") evi_name = evidence[0] inventory[i] = [args[0], args[1], args[2]] client.area.area_manager.set_character_data(target, "inventory", inventory) database.log_area("inventory.edit", client, client.area) charname = client.area.area_manager.char_list[target] if args[0] != "*" and target_evi != args[0]: - client.send_ooc( - f"You have edited evidence '{evi_name}' to '{args[0]}' in {charname}'s inventory.") + client.send_ooc(f"You have edited evidence '{evi_name}' to '{args[0]}' in {charname}'s inventory.") else: client.send_ooc(f"You have edited evidence '{evi_name}' in {charname}'s inventory.") except ValueError: diff --git a/server/commands/messaging.py b/server/commands/messaging.py index c5581a7f..99b8b247 100644 --- a/server/commands/messaging.py +++ b/server/commands/messaging.py @@ -155,19 +155,13 @@ def ooc_cmd_pm(client, arg): raise ArgumentError( 'Not enough arguments. use /pm <target> <message>. Target should be ID, OOC-name or char-name. Use /getarea for getting info like "[ID] char-name".' ) - targets = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, arg, local=True - ) + targets = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, arg, local=True) key = TargetType.CHAR_NAME if len(targets) == 0 and args[0].isdigit(): - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), all_hub=True - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), all_hub=True) key = TargetType.ID if len(targets) == 0: - targets = client.server.client_manager.get_targets( - client, TargetType.OOC_NAME, arg, local=True - ) + targets = client.server.client_manager.get_targets(client, TargetType.OOC_NAME, arg, local=True) key = TargetType.OOC_NAME if len(targets) == 0: raise ArgumentError("No targets found.") @@ -176,12 +170,11 @@ def ooc_cmd_pm(client, arg): msg = " ".join(args[1:]) else: if key == TargetType.CHAR_NAME: - msg = arg[len(targets[0].char_name) + 1:] + msg = arg[len(targets[0].char_name) + 1 :] if key == TargetType.OOC_NAME: - msg = arg[len(targets[0].name) + 1:] + msg = arg[len(targets[0].name) + 1 :] except Exception: - raise ArgumentError( - "Not enough arguments. Use /pm <target> <message>.") + raise ArgumentError("Not enough arguments. Use /pm <target> <message>.") c = targets[0] if c.pm_mute: raise ClientError("This user muted all pm conversation") @@ -214,6 +207,4 @@ def ooc_cmd_mutepm(client, arg): if len(arg) != 0: raise ArgumentError("This command doesn't take any arguments") client.pm_mute = not client.pm_mute - client.send_ooc( - "You stopped receiving PMs" if client.pm_mute else "You are now receiving PMs" - ) + client.send_ooc("You stopped receiving PMs" if client.pm_mute else "You are now receiving PMs") diff --git a/server/commands/music.py b/server/commands/music.py index 74f7641f..b608e38b 100644 --- a/server/commands/music.py +++ b/server/commands/music.py @@ -49,9 +49,7 @@ def ooc_cmd_currentmusic(client, arg): ) else: client.send_ooc( - "The current music is '{}' and was played by {}.".format( - client.area.music, client.area.music_player - ) + "The current music is '{}' and was played by {}.".format(client.area.music, client.area.music_player) ) @@ -88,13 +86,9 @@ def ooc_cmd_jukebox_toggle(client, arg): client.area.jukebox = not client.area.jukebox client.area.jukebox_votes = [] client.area.broadcast_ooc( - "{} [{}] has set the jukebox to {}.".format( - client.showname, client.id, client.area.jukebox - ) - ) - database.log_area( - "jukebox_toggle", client, client.area, message=client.area.jukebox + "{} [{}] has set the jukebox to {}.".format(client.showname, client.id, client.area.jukebox) ) + database.log_area("jukebox_toggle", client, client.area, message=client.area.jukebox) @mod_only(area_owners=True) @@ -108,20 +102,15 @@ def ooc_cmd_jukebox_skip(client, arg): if not client.area.jukebox: raise ClientError("This area does not have a jukebox.") if len(client.area.jukebox_votes) == 0: - raise ClientError( - "There is no song playing right now, skipping is pointless.") + raise ClientError("There is no song playing right now, skipping is pointless.") client.area.start_jukebox() if len(client.area.jukebox_votes) == 1: client.area.broadcast_ooc( - "{} [{}] has forced a skip, restarting the only jukebox song.".format( - client.showname, client.id - ) + "{} [{}] has forced a skip, restarting the only jukebox song.".format(client.showname, client.id) ) else: client.area.broadcast_ooc( - "{} [{}] has forced a skip to the next jukebox song.".format( - client.showname, client.id - ) + "{} [{}] has forced a skip to the next jukebox song.".format(client.showname, client.id) ) database.log_area("jukebox_skip", client, client.area) @@ -172,8 +161,7 @@ def ooc_cmd_jukebox(client, arg): if total == 0: message += "-- CHANCE: 100" else: - message += "-- CHANCE: " + \ - str(round(chance[song] / total * 100)) + message += "-- CHANCE: " + str(round(chance[song] / total * 100)) client.send_ooc(f"The jukebox has the following songs in it:{message}") @@ -185,8 +173,7 @@ def ooc_cmd_play(client, arg): """ if len(arg) == 0: raise ArgumentError("You must specify a song.") - client.change_music(arg, client.char_id, "", 2, - True) # looped change music + client.change_music(arg, client.char_id, "", 2, True) # looped change music def ooc_cmd_play_once(client, arg): @@ -196,8 +183,7 @@ def ooc_cmd_play_once(client, arg): """ if len(arg) == 0: raise ArgumentError("You must specify a song.") - client.change_music(arg, client.char_id, "", 2, - False) # non-looped change music + client.change_music(arg, client.char_id, "", 2, False) # non-looped change music @mod_only() @@ -209,9 +195,7 @@ def ooc_cmd_blockdj(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target. Use /blockdj <id>.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must enter a number. Use /blockdj <id>.") if not targets: @@ -233,9 +217,7 @@ def ooc_cmd_unblockdj(client, arg): if len(arg) == 0: raise ArgumentError("You must specify a target. Use /unblockdj <id>.") try: - targets = client.server.client_manager.get_targets( - client, TargetType.ID, int(arg), False - ) + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False) except Exception: raise ArgumentError("You must enter a number. Use /unblockdj <id>.") if not targets: @@ -353,8 +335,7 @@ def ooc_cmd_hub_musiclist(client, arg): client.area.area_manager.load_music(f"storage/musiclists/{arg}.yaml") client.area.area_manager.music_ref = arg client.send_ooc(f"Loading hub musiclist {arg}...") - client.server.client_manager.refresh_music( - client.area.area_manager.clients) + client.server.client_manager.refresh_music(client.area.area_manager.clients) except AreaError: raise except Exception: @@ -368,14 +349,11 @@ def ooc_cmd_random_music(client, arg): """ songs = [] for c in client.local_music_list: - if "category" in c and ( - arg == "" or c["category"].strip("==").lower() == arg.lower() - ): + if "category" in c and (arg == "" or c["category"].strip("==").lower() == arg.lower()): if "songs" in c: songs = songs + c["songs"] if len(songs) <= 0: - raise ArgumentError( - "Could not find a single song that fit the criteria!") + raise ArgumentError("Could not find a single song that fit the criteria!") song_name = songs[random.randint(0, len(songs) - 1)]["name"] client.change_music(song_name, client.char_id, "", 2) @@ -383,19 +361,15 @@ def ooc_cmd_random_music(client, arg): def musiclist_rebuild(musiclist, path): prepath = "" for item in musiclist: - if ( - "use_unique_folder" in item - and item["use_unique_folder"] is True - ): - prepath = os.path.splitext( - os.path.basename(f"storage/musiclists/{path}"))[0] + "/" + if "use_unique_folder" in item and item["use_unique_folder"] is True: + prepath = os.path.splitext(os.path.basename(f"storage/musiclists/{path}"))[0] + "/" if "category" not in item: continue for song in item["songs"]: song["name"] = prepath + song["name"] - + return musiclist @@ -445,7 +419,7 @@ def ooc_cmd_musiclist_save(client, arg): os.remove(f"storage/musiclists/{name}.yaml") except OSError: raise AreaError(f"{args[0]} hasn't been removed from write and read folder!") - + with open(filepath, "w", encoding="utf-8") as yaml_save: yaml.dump(musiclist, yaml_save) client.send_ooc(f"Musiclist '{name}' saved on server list!") @@ -458,22 +432,20 @@ def ooc_cmd_musiclist_remove(client, arg): Usage: /musiclist_remove <local/area/hub> <Category> <MusicName> """ if arg == "": - client.send_ooc( - "Usage: /musiclist_remove <local/area/hub> <Category> <MusicName>" - ) + client.send_ooc("Usage: /musiclist_remove <local/area/hub> <Category> <MusicName>") return args = shlex.split(arg) if len(args) != 3: - client.send_ooc( - "Usage: /musiclist_remove <local/area/hub> <Category> <MusicName>" - ) + client.send_ooc("Usage: /musiclist_remove <local/area/hub> <Category> <MusicName>") return if args[0] not in ["local", "area", "hub"]: - client.send_ooc("You can add a song if musiclist is loaded in local or in area or in hub.\nUsage: /musiclist_add <local/area/hub> <Category> <MusicName>") + client.send_ooc( + "You can add a song if musiclist is loaded in local or in area or in hub.\nUsage: /musiclist_add <local/area/hub> <Category> <MusicName>" + ) return - + if args[0] == "local": targets = [client] musiclist = client.music_list @@ -489,7 +461,7 @@ def ooc_cmd_musiclist_remove(client, arg): return targets = client.area.area_manager.clients musiclist = client.area.area_manager.music_list - + if musiclist == []: client.send_ooc("You cannot remove a song, if there aren't songs in the musiclist.") return @@ -507,10 +479,7 @@ def ooc_cmd_musiclist_remove(client, arg): category_id = categories.index(args[1]) - songs = [ - musiclist[category_id]["songs"][i]["name"] - for i in range(0, len(musiclist[category_id]["songs"])) - ] + songs = [musiclist[category_id]["songs"][i]["name"] for i in range(0, len(musiclist[category_id]["songs"]))] if args[2] not in songs: client.send_ooc("Song has not been found!") @@ -530,7 +499,7 @@ def ooc_cmd_musiclist_remove(client, arg): else: path = client.area.area_manager.music_ref client.area.area_manager.music_list = musiclist_rebuild(musiclist, path) - + client.server.client_manager.refresh_music(targets, True) client.send_ooc(f"'{args[2]}' song has been removed to '{path}' musiclist.") @@ -543,24 +512,20 @@ def ooc_cmd_musiclist_add(client, arg): Usage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path] """ if arg == "": - client.send_ooc( - "Usage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path]" - ) + client.send_ooc("Usage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path]") return args = shlex.split(arg) if len(args) < 3: - client.send_ooc( - "Usage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path]" - ) + client.send_ooc("Usage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path]") return - + if args[0] not in ["local", "area", "hub"]: client.send_ooc( "You can add a song if musiclist is loaded in local or in area or in hub.\nUsage: /musiclist_add <local/area/hub> <Category> <MusicName> [Length] [Path]" ) return - + if args[0] == "local": targets = [client] musiclist = client.music_list @@ -576,7 +541,7 @@ def ooc_cmd_musiclist_add(client, arg): return targets = client.area.area_manager.clients musiclist = client.area.area_manager.music_list - + if musiclist == []: musiclist.append({}) musiclist[0]["use_unique_folder"] = False @@ -586,7 +551,7 @@ def ooc_cmd_musiclist_add(client, arg): client.area.music_ref = "unsaved" else: client.area.area_manager.music_ref = "unsaved" - + categories = [] for i in range(0, len(musiclist)): if "category" in musiclist[i]: @@ -606,8 +571,8 @@ def ooc_cmd_musiclist_add(client, arg): song_id = len(musiclist[category_id]["songs"]) - 1 name = args[2] - if not name.endswith(('.mp3', '.ogg', '.opus', '.wav', '.m4a')): - name += '.music' + if not name.endswith((".mp3", ".ogg", ".opus", ".wav", ".m4a")): + name += ".music" musiclist[category_id]["songs"][song_id]["name"] = name length = -1 @@ -631,6 +596,6 @@ def ooc_cmd_musiclist_add(client, arg): else: path = client.area.area_manager.music_ref client.area.area_manager.music_list = musiclist_rebuild(musiclist, path) - + client.server.client_manager.refresh_music(targets, True) client.send_ooc(f"'{args[2]}' song has been added to '{path}' musiclist.") diff --git a/server/commands/roleplay.py b/server/commands/roleplay.py index da8bd585..59431334 100644 --- a/server/commands/roleplay.py +++ b/server/commands/roleplay.py @@ -57,9 +57,7 @@ def rtd(arg): if arg_length == 2: dice_type, modifiers = args if len(modifiers) > MODIFIER_LENGTH_MAX: - raise ArgumentError( - "The given modifier is too long to compute. Please try a shorter one" - ) + raise ArgumentError("The given modifier is too long to compute. Please try a shorter one") elif arg_length == 1: dice_type, modifiers = arg, "" else: @@ -76,31 +74,20 @@ def rtd(arg): try: num_dice, chosen_max = int(dice_type[0]), int(dice_type[1]) except ValueError: - raise ArgumentError( - "Expected integer value for number of rolls and max value of dice" - ) + raise ArgumentError("Expected integer value for number of rolls and max value of dice") if not 1 <= num_dice <= NUMDICE_MAX: - raise ArgumentError( - "Number of rolls must be between 1 and {}".format(NUMDICE_MAX) - ) + raise ArgumentError("Number of rolls must be between 1 and {}".format(NUMDICE_MAX)) if not 1 <= chosen_max <= DICE_MAX: - raise ArgumentError( - "Dice value must be between 1 and {}".format(DICE_MAX)) + raise ArgumentError("Dice value must be between 1 and {}".format(DICE_MAX)) for char in modifiers: if char not in ACCEPTABLE_IN_MODIFIER: - raise ArgumentError( - "Expected numbers and standard mathematical operations in modifier" - ) + raise ArgumentError("Expected numbers and standard mathematical operations in modifier") if char == "r": special_calculation = True - if ( - "**" in modifiers - ): # Exponentiation manually disabled, it can be pretty dangerous - raise ArgumentError( - "Expected numbers and standard mathematical operations in modifier" - ) + if "**" in modifiers: # Exponentiation manually disabled, it can be pretty dangerous + raise ArgumentError("Expected numbers and standard mathematical operations in modifier") else: num_dice, chosen_max, modifiers = 1, 6, "" # Default @@ -134,22 +121,14 @@ def rtd(arg): "Given mathematical formula takes numbers past the server's computation limit" ) except ValueError: - raise ArgumentError( - "Given mathematical formula has a syntax error and cannot be computed" - ) + raise ArgumentError("Given mathematical formula has a syntax error and cannot be computed") try: - mid_roll = round( - eval(aux_modifier[:-1]) - ) # By this point it should be 'safe' to run eval + mid_roll = round(eval(aux_modifier[:-1])) # By this point it should be 'safe' to run eval except SyntaxError: - raise ArgumentError( - "Given mathematical formula has a syntax error and cannot be computed" - ) + raise ArgumentError("Given mathematical formula has a syntax error and cannot be computed") except TypeError: # Deals with inputs like 3(r-1) - raise ArgumentError( - "Given mathematical formula has a syntax error and cannot be computed" - ) + raise ArgumentError("Given mathematical formula has a syntax error and cannot be computed") except ZeroDivisionError: divzero_attempts += 1 if divzero_attempts == MAXDIVZERO_ATTEMPTS: @@ -191,9 +170,7 @@ def ooc_cmd_roll(client, arg): f"[👉🎲] [{client.id}] {client.showname} rolled:\n {roll} out of {chosen_max}." + (f"\nThe total sum is {Sum}." if num_dice > 1 else "") ) - database.log_area( - "roll", client, client.area, message=f"{roll} out of {chosen_max}" - ) + database.log_area("roll", client, client.area, message=f"{roll} out of {chosen_max}") def ooc_cmd_rollp(client, arg): @@ -207,20 +184,15 @@ def ooc_cmd_rollp(client, arg): roll, num_dice, chosen_max, _modifiers, Sum = rtd(arg) client.send_ooc( - f"[Hidden] You rolled {roll} out of {chosen_max}." - + (f"\nThe total sum is {Sum}." if num_dice > 1 else "") + f"[Hidden] You rolled {roll} out of {chosen_max}." + (f"\nThe total sum is {Sum}." if num_dice > 1 else "") ) for c in client.area.owners: - c.send_ooc( - f"[{client.area.id}]{client.showname} secretly rolled {roll} out of {chosen_max}." - ) + c.send_ooc(f"[{client.area.id}]{client.showname} secretly rolled {roll} out of {chosen_max}.") client.area.send_owner_command( f"[👉🎲] [{client.id}] {client.showname} secretly rolled:\n {roll} out of {chosen_max}." + (f"\nThe total sum is {Sum}." if num_dice > 1 else "") ) - database.log_area( - "rollp", client, client.area, message=f"{roll} out of {chosen_max}" - ) + database.log_area("rollp", client, client.area, message=f"{roll} out of {chosen_max}") def ooc_cmd_notecard(client, arg): @@ -248,9 +220,7 @@ def ooc_cmd_notecard_clear(client, arg): Usage: /notecard_clear """ client.area.cards.clear() - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} has cleared all the note cards in this area." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} has cleared all the note cards in this area.") database.log_area("notecard_clear", client, client.area) @@ -268,14 +238,12 @@ def ooc_cmd_notecard_reveal(client, arg): card_showname = card_data[0] card_msg = card_data[1] card_owner_display = f"[DC] {card_showname}" - card_owner = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, card_charname, False - ) + card_owner = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, card_charname, False) if len(card_owner) > 0: card_owner = card_owner[0] card_owner_display = f"[{card_owner.id}] {card_owner.showname}" msg += f"\n{card_owner_display}: {card_msg}" - + # Reveal the notecards in OOC! client.area.broadcast_ooc(msg) @@ -284,9 +252,7 @@ def ooc_cmd_notecard_reveal(client, arg): client.send_ooc("Use /notecard_clear for clearing.") else: client.area.cards.clear() - client.area.broadcast_ooc( - "Notecards have been cleared." - ) + client.area.broadcast_ooc("Notecards have been cleared.") database.log_area("notecard_reveal", client, client.area) @@ -299,26 +265,20 @@ def ooc_cmd_notecard_check(client, arg): """ if len(client.area.cards) == 0: raise ClientError("There are no notecards to check in this area.") - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} has checked the notecards in this area." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} has checked the notecards in this area.") msg = "Notecards in this area:" for card_charname, card_data in client.area.cards.items(): card_showname = card_data[0] card_msg = card_data[1] card_owner_display = f"[DC] {card_showname}" - card_owner = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, card_charname, False - ) + card_owner = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, card_charname, False) if len(card_owner) > 0: card_owner = card_owner[0] card_owner_display = f"[{card_owner.id}] {card_owner.showname}" msg += f"\n{card_owner_display}: {card_msg}" client.send_ooc(msg) - client.send_ooc( - "Use /notecard_clear for clearing, or /notecard_reveal to reveal the results publicly." - ) + client.send_ooc("Use /notecard_clear for clearing, or /notecard_reveal to reveal the results publicly.") database.log_area("notecard_check", client, client.area) @@ -331,12 +291,8 @@ def ooc_cmd_vote(client, arg): if len(args) == 0: raise ArgumentError("Please provide a client ID. Usage: /vote <id>.") if client.char_name in [y for x in client.area.votes.values() for y in x]: - raise ArgumentError( - "You already cast your vote! Wait on the CM to /vote_clear." - ) - target = client.server.client_manager.get_targets( - client, TargetType.ID, int(args[0]), False - )[0] + raise ArgumentError("You already cast your vote! Wait on the CM to /vote_clear.") + target = client.server.client_manager.get_targets(client, TargetType.ID, int(args[0]), False)[0] client.area.votes.setdefault(target.char_name, []).append(client.char_name) client.area.broadcast_ooc(f"[{client.id}] {client.showname} cast a vote.") database.log_area("vote", client, client.area) @@ -353,26 +309,20 @@ def ooc_cmd_vote_clear(client, arg): for value in client.area.votes.values(): if arg in value: value.remove(arg) - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} has cleared {arg}'s vote." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} has cleared {arg}'s vote.") return raise ClientError( f"No vote was cast by {arg}! (This is case-sensitive - are you sure you spelled the voter character folder right?)" ) client.area.votes.clear() - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} has cleared all the votes in this area." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} has cleared all the votes in this area.") database.log_area("vote_clear", client, client.area) def get_vote_results(client, votes): def get_showname(target_name): """Helper function to convert a charname to showname format.""" - owners = client.server.client_manager.get_targets( - client, TargetType.CHAR_NAME, target_name, False - ) + owners = client.server.client_manager.get_targets(client, TargetType.CHAR_NAME, target_name, False) if owners: owner = owners[0] return f"[{owner.id}] {owner.showname}" @@ -380,38 +330,36 @@ def get_showname(target_name): # Sort votes by count (ascending) sorted_votes = sorted(votes.items(), key=lambda x: len(x[1])) - + msg_lines = [] max_votes = 0 - + for candidate, voters in sorted_votes: # Process voters voter_names = [get_showname(voter) for voter in voters] voters_str = ", ".join(voter_names) - + # Process candidate candidate_name = get_showname(candidate) - + # Build vote line vote_count = len(voters) plural = "s" if vote_count > 1 else "" - msg_lines.append( - f"🗳️{vote_count} vote{plural} for {candidate_name}\n   ◽ Voted by {voters_str}." - ) - + msg_lines.append(f"🗳️{vote_count} vote{plural} for {candidate_name}\n   ◽ Voted by {voters_str}.") + # Track max votes if vote_count > max_votes: max_votes = vote_count - + # Find winners winners = [get_showname(k) for k, v in sorted_votes if len(v) == max_votes] - + # Add winner/tie message if len(winners) > 1: msg_lines.append(f"{', '.join(winners)} have tied for most votes.") else: msg_lines.append(f"{winners[0]} has most votes.") - + return "\n".join(msg_lines) @@ -432,9 +380,7 @@ def ooc_cmd_vote_reveal(client, arg): client.send_ooc("Use /vote_clear for clearing.") else: client.area.votes.clear() - client.area.broadcast_ooc( - "Votes have been cleared." - ) + client.area.broadcast_ooc("Votes have been cleared.") database.log_area("vote_reveal", client, client.area) @@ -447,15 +393,11 @@ def ooc_cmd_vote_check(client, arg): """ if len(client.area.votes) == 0: raise ClientError("There are no votes to check in this area.") - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} has checked the votes in this area." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} has checked the votes in this area.") msg = "Votes in this area:\n" msg += get_vote_results(client, client.area.votes) client.send_ooc(msg) - client.send_ooc( - "Use /vote_clear for clearing, or /vote_reveal to reveal the results publicly." - ) + client.send_ooc("Use /vote_clear for clearing, or /vote_reveal to reveal the results publicly.") database.log_area("vote_check", client, client.area) @@ -477,9 +419,7 @@ def rolla_reload(area): with open("config/dice.yaml", "r") as dice: area.ability_dice = yaml.safe_load(dice) except Exception: - raise ServerError( - "There was an error parsing the ability dice configuration. Check your syntax." - ) + raise ServerError("There was an error parsing the ability dice configuration. Check your syntax.") def ooc_cmd_rolla_set(client, arg): @@ -491,13 +431,9 @@ def ooc_cmd_rolla_set(client, arg): rolla_reload(client.area) available_sets = ", ".join(client.area.ability_dice.keys()) if len(arg) == 0: - raise ArgumentError( - f"You must specify the ability set name.\nAvailable sets: {available_sets}" - ) + raise ArgumentError(f"You must specify the ability set name.\nAvailable sets: {available_sets}") elif arg not in client.area.ability_dice: - raise ArgumentError( - f"Invalid ability set '{arg}'.\nAvailable sets: {available_sets}" - ) + raise ArgumentError(f"Invalid ability set '{arg}'.\nAvailable sets: {available_sets}") client.ability_dice_set = arg client.send_ooc(f"Set ability set to {arg}.") @@ -517,16 +453,11 @@ def ooc_cmd_rolla(client, arg): if not hasattr(client.area, "ability_dice"): rolla_reload(client.area) if not hasattr(client, "ability_dice_set"): - raise ClientError( - "You must set your ability set using /rolla_set <name>.") + raise ClientError("You must set your ability set using /rolla_set <name>.") ability_dice = client.area.ability_dice[client.ability_dice_set] roll, max_roll, ability = rolla(ability_dice) - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} rolled a {roll} (out of {max_roll}): {ability}." - ) - database.log_area( - "rolla", client, client.area, message=f"{roll} out of {max_roll}: {ability}" - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} rolled a {roll} (out of {max_roll}): {ability}.") + database.log_area("rolla", client, client.area, message=f"{roll} out of {max_roll}: {ability}") def ooc_cmd_coinflip(client, arg): @@ -538,9 +469,7 @@ def ooc_cmd_coinflip(client, arg): raise ArgumentError("This command has no arguments.") coin = ["heads", "tails"] flip = random.choice(coin) - client.area.broadcast_ooc( - f"[{client.id}] {client.showname} flipped a coin and got {flip}." - ) + client.area.broadcast_ooc(f"[{client.id}] {client.showname} flipped a coin and got {flip}.") database.log_area("coinflip", client, client.area, message=flip) @@ -576,9 +505,9 @@ def ooc_cmd_rps(client, arg): rps_rules = client.area.area_manager.rps_rules - # Strip the input of blank spaces on edges + # Strip the input of blank spaces on edges arg = arg.strip() - + # If doing /rps by itself, simply tell the user the rules. if not arg: msg = "RPS rules:" @@ -587,16 +516,18 @@ def ooc_cmd_rps(client, arg): choice = rule[0] msg += choice if len(choice) > 1: - losers = ', '.join(rule[1:]) + losers = ", ".join(rule[1:]) msg += f" beats {losers}" client.send_ooc(msg) return if arg.lower() in ["clear", "cancel"]: if client.rps_choice: - client.area.broadcast_ooc(f'[{client.id}] {client.showname} no longer wants to play 🎲Rock Paper Scissors🎲... 🙁') + client.area.broadcast_ooc( + f"[{client.id}] {client.showname} no longer wants to play 🎲Rock Paper Scissors🎲... 🙁" + ) client.rps_choice = "" - client.send_ooc('You cleared your choice.') + client.send_ooc("You cleared your choice.") return # List of our available choices @@ -614,16 +545,16 @@ def ooc_cmd_rps(client, arg): # Fuzzy match, queue up our pick but look if we can get something better if arg.lower() in choice: picked = choice - + if picked not in choices: raise ArgumentError(f"Invalid choice! Available choices are: {', '.join(choices)}") - + # If we already have made a rps choice before, simply silently swap our choice. if client.rps_choice: client.rps_choice = picked - client.send_ooc(f'Swapped your choice to {client.rps_choice}!') + client.send_ooc(f"Swapped your choice to {client.rps_choice}!") return - + # Set our Rock Paper Scissors choice client.rps_choice = picked @@ -639,16 +570,16 @@ def ooc_cmd_rps(client, arg): # Look for our opponent if none is present if not target: - msg = f'[{client.id}] {client.showname} wants to play 🎲Rock Paper Scissors🎲!\n❕ Do /rps [choice] to challenge them! ❕' + msg = f"[{client.id}] {client.showname} wants to play 🎲Rock Paper Scissors🎲!\n❕ Do /rps [choice] to challenge them! ❕" client.area.broadcast_ooc(msg) - client.send_ooc(f'You picked {client.rps_choice}!') + client.send_ooc(f"You picked {client.rps_choice}!") return # Start constructing our output message - msg = '🎲Rock Paper Scissors🎲' - msg += f'\n  ◽ [{target.id}] {target.showname} picks {target.rps_choice}!' - msg += f'\n  ◽ [{client.id}] {client.showname} picks {client.rps_choice}!' - + msg = "🎲Rock Paper Scissors🎲" + msg += f"\n  ◽ [{target.id}] {target.showname} picks {target.rps_choice}!" + msg += f"\n  ◽ [{client.id}] {client.showname} picks {client.rps_choice}!" + # Calculate our winner a = target.rps_choice.lower() b = client.rps_choice.lower() @@ -689,16 +620,16 @@ def ooc_cmd_rps_rules(client, arg): /rps_rules <del|remove|-> [index] - delete a rule at index /rps_rules <clear|clean|reset|wipe> - wipe all current rules """ - #client.area.area_manager.rps_rules + # client.area.area_manager.rps_rules - # Strip the input of blank spaces on edges + # Strip the input of blank spaces on edges arg = arg.strip() - + # If doing /rps_rules by itself, simply tell the user the rules. if not arg: ooc_cmd_rps(client, "") return - + try: args = arg.split(maxsplit=1) action = args[0] @@ -714,24 +645,18 @@ def ooc_cmd_rps_rules(client, arg): client.area.area_manager.rps_rules.append(newrule) client.send_ooc(f"Added a new rule: {rule}") elif action.lower() in ["del", "remove", "-"]: - index = int(param)-1 + index = int(param) - 1 if index < 0 or index >= len(client.area.area_manager.rps_rules): - raise ArgumentError( - "Invalid index!" - ) + raise ArgumentError("Invalid index!") client.send_ooc(f"Deleted a rule: {client.area.area_manager.rps_rules[index]}") client.area.area_manager.rps_rules.pop(index) elif action.lower() in ["clear", "clean", "reset", "wipe"]: client.send_ooc("Deleted all rules.") client.area.area_manager.rps_rules.clear() else: - raise ArgumentError( - "Invalid action!" - ) + raise ArgumentError("Invalid action!") except ValueError: - raise ArgumentError( - "Invalid parameter!" - ) + raise ArgumentError("Invalid parameter!") def ooc_cmd_timer(client, arg): @@ -788,8 +713,7 @@ def ooc_cmd_timer(client, arg): if len(args) < 2: if timer.set: if timer.started: - client.send_ooc( - f"Timer {timer_id} is at {timer.target - arrow.get()}") + client.send_ooc(f"Timer {timer_id} is at {timer.target - arrow.get()}") else: client.send_ooc(f"Timer {timer_id} is at {timer.static}") else: @@ -797,15 +721,9 @@ def ooc_cmd_timer(client, arg): return if client not in client.area.owners and not client.is_mod: - raise ArgumentError( - "Only CMs or GMs can modify timers. Usage: /timer <id>") - if ( - timer_id == 0 - and client not in client.area.area_manager.owners - and not client.is_mod - ): - raise ArgumentError( - "Only GMs can set hub-wide timer ID 0. Usage: /timer <id>") + raise ArgumentError("Only CMs or GMs can modify timers. Usage: /timer <id>") + if timer_id == 0 and client not in client.area.area_manager.owners and not client.is_mod: + raise ArgumentError("Only GMs can set hub-wide timer ID 0. Usage: /timer <id>") command_arg = args[1] @@ -869,15 +787,11 @@ def ooc_cmd_timer(client, arg): cmd = full.split(" ")[0] called_function = f"ooc_cmd_{cmd}" - if len(client.server.command_aliases) > 0 and not hasattr( - commands, called_function - ): + if len(client.server.command_aliases) > 0 and not hasattr(commands, called_function): if cmd in client.server.command_aliases: called_function = f"ooc_cmd_{client.server.command_aliases[cmd]}" if not hasattr(commands, called_function): - client.send_ooc( - f"[Timer {timer_id}] Invalid command: {cmd}. Use /help to find up-to-date commands." - ) + client.send_ooc(f"[Timer {timer_id}] Invalid command: {cmd}. Use /help to find up-to-date commands.") return timer.commands.append(full) client.send_ooc(f"Adding command to Timer {timer_id}: /{full}") @@ -934,12 +848,7 @@ def ooc_cmd_demo(client, arg): client.last_demo_call = time.time() * 1000 client.area.demo.clear() - desc = ( - evidence.desc.replace("<num>", "#") - .replace("<and>", "&") - .replace("<percent>", "%") - .replace("<dollar>", "$") - ) + desc = evidence.desc.replace("<num>", "#").replace("<and>", "&").replace("<percent>", "%").replace("<dollar>", "$") packets = desc.split("%") for packet in packets: p_args = packet.split("#") @@ -952,8 +861,7 @@ def ooc_cmd_demo(client, arg): client.area.demo += [p_args] for c in client.area.clients: if c in client.area.owners: - c.send_ooc( - f"Starting demo playback using evidence '{evidence.name}'...") + c.send_ooc(f"Starting demo playback using evidence '{evidence.name}'...") client.area.play_demo(client) @@ -997,8 +905,7 @@ def ooc_cmd_trigger(client, arg): if not evidence: raise ArgumentError("Target evidence not found!") if len(args) <= 2: - client.send_ooc( - f'Call "{evidence.triggers[trig]}" on trigger "{trig}"') + client.send_ooc(f'Call "{evidence.triggers[trig]}" on trigger "{trig}"') return val = args[2] evidence.triggers[trig] = val @@ -1009,8 +916,7 @@ def ooc_cmd_trigger(client, arg): if trig not in client.area.triggers: raise ArgumentError(f"Invalid trigger: {trig}") if len(args) <= 1: - client.send_ooc( - f'Call "{client.area.triggers[trig]}" on trigger "{trig}"') + client.send_ooc(f'Call "{client.area.triggers[trig]}" on trigger "{trig}"') return val = args[1] client.area.triggers[trig] = val @@ -1036,16 +942,12 @@ def ooc_cmd_format_timer(client, arg): client.send_ooc("You cannot change timer 0 format if you are not GM") return else: - if ( - client.is_mod - or client in client.area.area_manager.owners - or client in client.area.owners - ): + if client.is_mod or client in client.area.area_manager.owners or client in client.area.owners: timer = client.area.timers[args[0] - 1] else: client.send_ooc("You cannot change timer format if you are at least CM") return - timer.format = ' '.join(args[1:]) + timer.format = " ".join(args[1:]) if timer.format == "": timer.format = "hh:mm:ss.zzz" client.send_ooc("Resetting timer format to default.") @@ -1081,11 +983,7 @@ def ooc_cmd_timer_interval(client, arg): client.send_ooc("You cannot change timer 0 interval if you are not GM") return else: - if ( - client.is_mod - or client in client.area.area_manager.owners - or client in client.area.owners - ): + if client.is_mod or client in client.area.area_manager.owners or client in client.area.owners: timer = client.area.timers[args[0] - 1] else: client.send_ooc("You cannot change timer interval if you are at least CM") @@ -1101,6 +999,7 @@ def ooc_cmd_timer_interval(client, arg): client.send_timer_set_interval(args[0], timer) client.send_ooc(f"Timer {args[0]} interval is set to '{args[1]}'") + def ooc_cmd_ooc_actions(client, arg): """ Enable or disable IC actions being broadcast to OOC as well. @@ -1108,9 +1007,7 @@ def ooc_cmd_ooc_actions(client, arg): Usage: /ooc_actions [tog] """ if len(arg.split()) > 1: - raise ArgumentError( - "This command can only take one argument ('on' or 'off') or no arguments at all!" - ) + raise ArgumentError("This command can only take one argument ('on' or 'off') or no arguments at all!") if arg: if arg == "on": client.ooc_actions = True diff --git a/server/constants.py b/server/constants.py index ca42708f..68109e6f 100644 --- a/server/constants.py +++ b/server/constants.py @@ -22,10 +22,10 @@ class MusicEffect(IntFlag): class ReportCardReason(IntFlag): - Nothing = 0, - Blackout = 1, - PendingLook = 2, - Blinded = 3, + Nothing = (0,) + Blackout = (1,) + PendingLook = (2,) + Blinded = (3,) NoPlayerList = 4 @@ -70,8 +70,7 @@ def censor(text, censor_list=[], replace="*", whole_words=True): if whole_words: regex = r"\b%s\b" for word in censor_list: - text = re.sub(regex % word, len(word) * replace, - text, flags=re.IGNORECASE) + text = re.sub(regex % word, len(word) * replace, text, flags=re.IGNORECASE) return text @@ -101,16 +100,12 @@ def encode_ao_packet(params): new_params.append(tuple(encoded)) else: new_params.append( - str(arg) - .replace("#", "<num>") - .replace("%", "<percent>") - .replace("$", "<dollar>") - .replace("&", "<and>") + str(arg).replace("#", "<num>").replace("%", "<percent>").replace("$", "<dollar>").replace("&", "<and>") ) return new_params + def derelative(sample): - while '../' in sample or '/..' in sample or '..\\' in sample or '\\..' in sample: - sample = sample.replace( - "../", "").replace("/..", "").replace("..\\", "").replace("\\..", "") - return sample \ No newline at end of file + while "../" in sample or "/.." in sample or "..\\" in sample or "\\.." in sample: + sample = sample.replace("../", "").replace("/..", "").replace("..\\", "").replace("\\..", "") + return sample diff --git a/server/database.py b/server/database.py index ab9e56b2..e5c10b61 100644 --- a/server/database.py +++ b/server/database.py @@ -60,21 +60,17 @@ def migrate_json_to_v1(self): # the same IPID, so we have to reassign those IPIDs. ip_ipids = json.loads(ipids_file.read()) ipids = set([ipid for ip, ipid in ip_ipids.items()]) - next_fallback_id = reduce( - lambda max_ipid, ipid: max(max_ipid, ipid), ipids - ) + next_fallback_id = reduce(lambda max_ipid, ipid: max(max_ipid, ipid), ipids) for ip, ipid in ip_ipids.items(): ipids.add(ipid) effective_id = ipid while True: try: conn.execute( - dedent( - """ + dedent(""" INSERT INTO ipids(ipid, ip_address) VALUES (?, ?) - """ - ), + """), (effective_id, ip), ) except sqlite3.IntegrityError: @@ -83,9 +79,7 @@ def migrate_json_to_v1(self): next_fallback_id = effective_id else: if effective_id != ipid: - logger.debug( - "IPID %s reassigned to %s", ipid, effective_id - ) + logger.debug("IPID %s reassigned to %s", ipid, effective_id) break with open("storage/hd_ids.json", "r") as hdids_file: @@ -95,17 +89,13 @@ def migrate_json_to_v1(self): # Sometimes, there are HDID entries that do not # correspond to any IPIDs in the IPID table. if ipid not in ipids: - logger.debug( - "IPID %s in HDID list does not exist. Ignoring.", ipid - ) + logger.debug("IPID %s in HDID list does not exist. Ignoring.", ipid) continue conn.execute( - dedent( - """ + dedent(""" INSERT OR IGNORE INTO hdids(hdid, ipid) VALUES (?, ?) - """ - ), + """), (hdid, ipid), ) @@ -118,30 +108,23 @@ def migrate_json_to_v1(self): try: ipid = int(ipid) except ValueError: - logger.debug( - "Bad IPID %s in ban list. Ignoring.", ipid) + logger.debug("Bad IPID %s in ban list. Ignoring.", ipid) continue if ipid not in ipids: - logger.debug( - "IPID %s in ban list does not exist. Ignoring.", ipid - ) + logger.debug("IPID %s in ban list does not exist. Ignoring.", ipid) continue ban_id = conn.execute( - dedent( - """ + dedent(""" INSERT INTO bans(ban_id, reason) VALUES (NULL, ?) - """ - ), + """), (ban_info["Reason"],), ).lastrowid conn.execute( - dedent( - """ + dedent(""" INSERT INTO ip_bans(ipid, ban_id) VALUES (?, ?) - """ - ), + """), (ipid, ban_id), ) @@ -153,8 +136,7 @@ def migrate(self): def migrate_to_version(self, version): with self.db as conn: - cur_version = conn.execute("PRAGMA user_version").fetchone()[ - "user_version"] + cur_version = conn.execute("PRAGMA user_version").fetchone()["user_version"] if cur_version >= version: return @@ -166,19 +148,15 @@ def ipid(self, ip): """Get an IPID from an IP address.""" with self.db as conn: conn.execute( - dedent( - """ + dedent(""" INSERT OR IGNORE INTO ipids(ipid, ip_address) VALUES (NULL, ?) - """ - ), + """), (ip,), ) ipid = conn.execute( - dedent( - """ + dedent(""" SELECT ipid FROM ipids WHERE ip_address = ? - """ - ), + """), (ip,), ).fetchone()["ipid"] return ipid @@ -187,11 +165,9 @@ def add_hdid(self, ipid, hdid): """Associate an HDID with an IPID.""" with self.db as conn: conn.execute( - dedent( - """ + dedent(""" INSERT OR IGNORE INTO hdids(hdid, ipid) VALUES (?, ?) - """ - ), + """), (hdid, ipid), ) @@ -211,41 +187,30 @@ def ban( """ with self.db as conn: if ban_id is None: - logger.info( - f"{banned_by.name} ({banned_by.ipid}) " - + f"banned {target_id}: '{reason}'." - ) + logger.info(f"{banned_by.name} ({banned_by.ipid}) " + f"banned {target_id}: '{reason}'.") ban_id = conn.execute( - dedent( - """ + dedent(""" INSERT INTO bans(reason, banned_by, unban_date) VALUES (?, ?, ?) - """ - ), + """), (reason, banned_by.ipid, unban_date), ).lastrowid if ban_type == "ipid": try: conn.execute( - dedent( - """ + dedent(""" INSERT INTO ip_bans(ipid, ban_id) VALUES (?, ?) - """ - ), + """), (target_id, ban_id), ) except sqlite3.IntegrityError as exc: - raise ServerError( - f"Error inserting ban: {exc}" " (the IPID may not exist)" - ) + raise ServerError(f"Error inserting ban: {exc}" " (the IPID may not exist)") elif ban_type == "hdid": try: conn.execute( - dedent( - """ + dedent(""" INSERT INTO hdid_bans(hdid, ban_id) VALUES (?, ?) - """ - ), + """), (target_id, ban_id), ) except sqlite3.IntegrityError as exc: @@ -264,13 +229,11 @@ def last_known_name(self, ipid): """ with self.db as conn: row = conn.execute( - dedent( - """ + dedent(""" SELECT ooc_name FROM area_events WHERE ipid = ? AND ooc_name IS NOT NULL AND ooc_name != '' ORDER BY event_time DESC LIMIT 1 - """ - ), + """), (ipid,), ).fetchone() if row is not None: @@ -298,11 +261,9 @@ def ipids(self): return [ row["ipid"] for row in conn.execute( - dedent( - """ + dedent(""" SELECT ipid FROM ip_bans WHERE ban_id = ? - """ - ), + """), (self.ban_id,), ).fetchall() ] @@ -314,11 +275,9 @@ def hdids(self): return [ row["hdid"] for row in conn.execute( - dedent( - """ + dedent(""" SELECT hdid FROM hdid_bans WHERE ban_id = ? - """ - ), + """), (self.ban_id,), ).fetchall() ] @@ -345,8 +304,7 @@ def find_ban(self, ipid=None, hdid=None, ban_id=None): # ooc_name IS NOT NULL # ORDER BY event_time DESC LIMIT 1 ban = conn.execute( - dedent( - """ + dedent(""" SELECT * FROM ( SELECT ban_id FROM ip_bans WHERE ipid = ? @@ -354,8 +312,7 @@ def find_ban(self, ipid=None, hdid=None, ban_id=None): UNION SELECT ban_id FROM bans WHERE ban_id = ? ) JOIN bans USING (ban_id) - """ - ), + """), (ipid, hdid, ban_id), ).fetchone() if ban is not None: @@ -368,11 +325,9 @@ def unban(self, ban_id): logger.info("Unbanning %s", ban_id) with self.db as conn: unbans = conn.execute( - dedent( - """ + dedent(""" DELETE FROM bans WHERE ban_id = ? - """ - ), + """), (ban_id,), ).rowcount return unbans > 0 @@ -389,13 +344,11 @@ def schedule_unbans(self): dated_bans = [] with self.db as conn: dated_bans = conn.execute( - dedent( - """ + dedent(""" SELECT ban_id FROM bans WHERE unban_date IS NOT NULL AND datetime(unban_date) < datetime(?, '+12 hours') - """ - ), + """), (arrow.utcnow().datetime,), ).fetchall() @@ -405,16 +358,12 @@ def schedule_unbans(self): def _schedule_unban(self, ban_id): with self.db as conn: ban = conn.execute( - dedent( - """ + dedent(""" SELECT unban_date FROM bans WHERE ban_id = ? - """ - ), + """), (ban_id,), ).fetchone() - time_to_unban = ( - arrow.get(ban["unban_date"]) - arrow.utcnow() - ).total_seconds() + time_to_unban = (arrow.get(ban["unban_date"]) - arrow.utcnow()).total_seconds() def auto_unban(): self.unban(ban_id) @@ -428,9 +377,7 @@ def log_area(self, event_subtype, client, area, message=None, target=None): value, creating one if necessary. """ ipid, char_name, ooc_name = ( - (client.ipid, client.char_name, client.name) - if client is not None - else (None, None, None) + (client.ipid, client.char_name, client.name) if client is not None else (None, None, None) ) target_ipid = target.ipid if target is not None else None subtype_id = self._subtype_atom("area", event_subtype) @@ -447,13 +394,11 @@ def log_area(self, event_subtype, client, area, message=None, target=None): ) with self.db as conn: conn.execute( - dedent( - """ + dedent(""" INSERT INTO area_events(ipid, hub_id, hub_name, area_id, area_name, ic_name, char_name, ooc_name, event_subtype, message, target_ipid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """ - ), + """), ( ipid, area.area_manager.id, @@ -472,16 +417,13 @@ def log_area(self, event_subtype, client, area, message=None, target=None): def log_connect(self, client, failed=False): """Log a connect attempt.""" logger.info( - f"{client.ipid} (HDID: {client.hdid}) " - + f'{"was blocked from connecting" if failed else "connected"}.' + f"{client.ipid} (HDID: {client.hdid}) " + f'{"was blocked from connecting" if failed else "connected"}.' ) with self.db as conn: conn.execute( - dedent( - """ + dedent(""" INSERT INTO connect_events(ipid, hdid, failed) VALUES (?, ?, ?) - """ - ), + """), (client.ipid, client.hdid, failed), ) @@ -494,17 +436,14 @@ def log_misc(self, event_subtype, client=None, target=None, data=None): target_ipid = target.ipid if target is not None else None subtype_id = self._subtype_atom("misc", event_subtype) data_json = json.dumps(data) - logger.info( - "%s (%s onto %s): %s", event_subtype, client_ipid, target_ipid, data) + logger.info("%s (%s onto %s): %s", event_subtype, client_ipid, target_ipid, data) with self.db as conn: conn.execute( - dedent( - """ + dedent(""" INSERT INTO misc_events(ipid, target_ipid, event_subtype, event_data) VALUES (?, ?, ?, ?) - """ - ), + """), (client_ipid, target_ipid, subtype_id, data_json), ) @@ -516,14 +455,12 @@ def recent_bans(self, count=5): return [ Database.Ban(**row) for row in conn.execute( - dedent( - """ + dedent(""" SELECT * FROM (SELECT * FROM bans WHERE ban_date IS NOT NULL ORDER BY ban_date DESC LIMIT ?) ORDER BY ban_date ASC - """ - ), + """), (count,), ).fetchall() ] @@ -534,20 +471,16 @@ def _subtype_atom(self, event_type, event_subtype): with self.db as conn: conn.execute( - dedent( - f""" + dedent(f""" INSERT OR IGNORE INTO {event_type}_event_types(type_name) VALUES (?) - """ - ), + """), (event_subtype,), ) return conn.execute( - dedent( - f""" + dedent(f""" SELECT type_id FROM {event_type}_event_types WHERE type_name = ? - """ - ), + """), (event_subtype,), ).fetchone()["type_id"] diff --git a/server/discordbot.py b/server/discordbot.py index 634c9b6c..4a0221e1 100644 --- a/server/discordbot.py +++ b/server/discordbot.py @@ -35,19 +35,18 @@ async def init(self, token): except Exception as e: print(e) - def add_commands(self): @self.command() async def announcing(ctx, name=None, description=None, url=None, additional=None, when=None, where=None): desc = f"{ctx.author}" + " " + self.announce_description embed = discord.Embed(title=self.announce_title, description=desc, color=self.announce_color) - if name is not None: + if name is not None: embed.add_field(name="Announce Name:", value=name, inline=False) else: self.channel.send("Arguments error!\n!announcing name description url additional when where") return if description is not None: - embed.add_field(name="Description:", value=description,inline=False) + embed.add_field(name="Description:", value=description, inline=False) else: self.channel.send("Arguments error!\n!announcing name description url additional when where") return @@ -60,7 +59,7 @@ async def announcing(ctx, name=None, description=None, url=None, additional=None embed.add_field(name="When:", value=when, inline=True) if where is not None: embed.add_field(name="Where:", value=where, inline=True) - channel = discord.utils.get( self.guild.text_channels, name=self.announce_channel) + channel = discord.utils.get(self.guild.text_channels, name=self.announce_channel) if self.announce_ping: await channel.send(f"<@&{self.announce_role}>", embed=embed) else: @@ -112,7 +111,7 @@ async def gethubs(interaction: discord.Interaction): msg += "\n" msg += f"Current online: {number_players} clients\n" if len(msg) > 2000: - msgchunks = [msg[i:i+2000] for i in range(0, len(msg), 2000)] + msgchunks = [msg[i : i + 2000] for i in range(0, len(msg), 2000)] for chunk in msgchunks: await interaction.response.send_message(chunk) else: @@ -146,7 +145,7 @@ async def on_message(message): self.hub_id, self.area_id, ) - + def queue_message(self, name, message, charname, anim): base = None avatar_url = None @@ -157,12 +156,9 @@ def queue_message(self, name, message, charname, anim): if "embed_emotes" in self.server.config["bridgebot"]: embed_emotes = self.server.config["bridgebot"]["embed_emotes"] if base is not None: - avatar_url = base + \ - parse.quote("characters/" + charname + "/char_icon.png") + avatar_url = base + parse.quote("characters/" + charname + "/char_icon.png") if embed_emotes: - anim_url = base + parse.quote( - "characters/" + charname + "/" + anim + ".png" - ) + anim_url = base + parse.quote("characters/" + charname + "/" + anim + ".png") self.pending_messages.append([name, message, avatar_url, anim_url]) async def on_ready(self): @@ -175,9 +171,7 @@ async def on_ready(self): except Exception as e: print(e) self.guild = self.guilds[0] - self.channel = discord.utils.get( - self.guild.text_channels, name=self.target_channel - ) + self.channel = discord.utils.get(self.guild.text_channels, name=self.target_channel) await self.wait_until_ready() while True: @@ -202,9 +196,7 @@ async def send_char_message(self, name, message, avatar=None, image=None): embed.set_image(url=image) print(avatar, image) await webhook.send(message, username=name, avatar_url=avatar, embed=embed) - print( - f'[DiscordBridge] Sending message from "{name}" to "{self.channel.name}"' - ) + print(f'[DiscordBridge] Sending message from "{name}" to "{self.channel.name}"') except Forbidden: print( f'[DiscordBridge] Insufficient permissions - couldnt send char message "{name}: {message}" with avatar "{avatar}" to "{self.channel.name}"' diff --git a/server/emotes.py b/server/emotes.py index 310708b8..5a99eb52 100644 --- a/server/emotes.py +++ b/server/emotes.py @@ -30,10 +30,7 @@ def read_ini(self): char_path = path.join(char_dir, self.name, "char.ini") with open(char_path, encoding="utf-8-sig") as f: char_ini.read_file(f) - logger.info( - "Found char.ini for %s that can be used for iniswap restrictions!", - char_path - ) + logger.info("Found char.ini for %s that can be used for iniswap restrictions!", char_path) except FileNotFoundError: return @@ -43,9 +40,7 @@ def read_ini(self): for emote_id in range(1, int(char_ini["emotions"]["number"]) + 1): try: emote_id = str(emote_id) - _name, preanim, anim, _mod = char_ini["emotions"][ - str(emote_id) - ].split("#")[:4] + _name, preanim, anim, _mod = char_ini["emotions"][str(emote_id)].split("#")[:4] # if "soundn" in char_ini and emote_id in char_ini["soundn"]: # sfx = char_ini["soundn"][str(emote_id)] or "" # if sfx != "" and len(sfx) == 1: @@ -57,23 +52,25 @@ def read_ini(self): # sfx checking is not performed due to custom sfx being possible, so don't bother for now sfx = "" - self.emotes.add( - (preanim.lower(), anim.lower(), sfx.lower())) + self.emotes.add((preanim.lower(), anim.lower(), sfx.lower())) except KeyError as e: logger.warning( - "Broken key %s in character file %s. " - "This indicates a malformed character INI file.", e.args[0], char_path + "Broken key %s in character file %s. " "This indicates a malformed character INI file.", + e.args[0], + char_path, ) except KeyError as e: logger.warning( - "Unknown key %s in character file %s. " - "This indicates a malformed character INI file.", e.args[0], char_path + "Unknown key %s in character file %s. " "This indicates a malformed character INI file.", + e.args[0], + char_path, ) return except ValueError as e: logger.warning( - "Value error in character file %s:\n%ss\n" - "This indicates a malformed character INI file.", char_path, e + "Value error in character file %s:\n%ss\n" "This indicates a malformed character INI file.", + char_path, + e, ) return @@ -90,6 +87,10 @@ def validate(self, preanim, anim, sfx): # Loop through emotes for emote in self.emotes: # If we find an emote that matches all 3, allow it - if (preanim == "" or emote[0] == preanim) and (anim == "" or emote[1] == anim) and (sfx == "" or emote[2] == sfx): + if ( + (preanim == "" or emote[0] == preanim) + and (anim == "" or emote[1] == anim) + and (sfx == "" or emote[2] == sfx) + ): return True return False diff --git a/server/evidence.py b/server/evidence.py index aefccb58..0e027b3d 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -1,16 +1,16 @@ - from server import commands from server.exceptions import ClientError, AreaError, ArgumentError, ServerError + class EvidenceList: """Contains a list of evidence items.""" limit = 50 - + class Evidence: """Represents a single evidence item.""" - def __init__(self, name, desc, image, pos, can_hide_in=False, show_in_dark=0, triggers = None): + def __init__(self, name, desc, image, pos, can_hide_in=False, show_in_dark=0, triggers=None): self.name = name self.desc = desc self.image = image @@ -54,7 +54,7 @@ def trigger(self, area, trig, target): if len(area.owners) <= 0: return - + if trig not in self.triggers: return @@ -118,11 +118,7 @@ def login(self, client): return True elif client.area.evidence_mod == "Mods" and not client.is_mod: return False - elif ( - client.area.evidence_mod == "CM" - and client not in client.area.owners - and not client.is_mod - ): + elif client.area.evidence_mod == "CM" and client not in client.area.owners and not client.is_mod: return False return True @@ -150,7 +146,7 @@ def parse_desc(self, desc): show_in_dark = 0 matches = 0 for line in lines: - cmd = line.strip(" ") # remove all whitespace + cmd = line.strip(" ") # remove all whitespace if cmd.startswith("<") and cmd.endswith(">"): args = cmd.strip("<>").split("=") if len(args) < 2: @@ -170,7 +166,7 @@ def parse_desc(self, desc): # Remvoes N lines, where N is how many <> we matched. Can't be more than 3. while matches > 0: # Truncates from the start of newline - desc = desc[desc.find("\n")+1:] + desc = desc[desc.find("\n") + 1 :] matches -= 1 return desc, poses, can_hide_in, show_in_dark @@ -191,9 +187,7 @@ def add_evidence(self, client, name, desc, image): if client.area.dark: return if len(self.evidences) >= self.limit: - client.send_ooc( - f"You can't have more than {self.limit} evidence items at a time." - ) + client.send_ooc(f"You can't have more than {self.limit} evidence items at a time.") return can_hide_in = False show_in_dark = 0 @@ -208,8 +202,7 @@ def add_evidence(self, client, name, desc, image): if len(client.area.pos_lock) > 0 and client.pos in client.area.pos_lock: pos = client.pos - self.evidences.append(self.Evidence( - name, desc, image, pos, can_hide_in, show_in_dark)) + self.evidences.append(self.Evidence(name, desc, image, pos, can_hide_in, show_in_dark)) id = len(self.evidences) # Inform the CMs of evidence manupulation client.area.send_owner_command( @@ -281,10 +274,7 @@ def create_evi_list(self, client): can_hide_in = int(evi.can_hide_in) show_in_dark = int(evi.show_in_dark) desc = f"<owner={evi.pos}>\n<can_hide_in={can_hide_in}>\n<show_in_dark={show_in_dark}>\n{evi.desc}" - evi_list.append( - self.Evidence(evi.name, desc, evi.image, - evi.pos).to_tuple() - ) + evi_list.append(self.Evidence(evi.name, desc, evi.image, evi.pos).to_tuple()) elif self.can_see(self.evidences[i], client.pos): # show_in_dark: # 0 - Do not show evidence in dark areas. @@ -315,8 +305,7 @@ def import_evidence(self, data): show_in_dark = int(evi["show_in_dark"]) if "triggers" in evi: triggers = evi["triggers"] - self.evidences.append(self.Evidence( - name, desc, image, pos, can_hide_in, show_in_dark, triggers)) + self.evidences.append(self.Evidence(name, desc, image, pos, can_hide_in, show_in_dark, triggers)) def export_evidence(self): return [e.to_dict() for e in self.evidences] @@ -416,8 +405,7 @@ def edit_evidence(self, client, id, arg): 'You entered a bad pos - evidence hidden! Make sure to have <owner=pos> at the top, where "pos" is the /pos this evidence should show up in. Put in "all" if you want it to show up in all pos, or "hidden" for no pos.' ) pos = "hidden" - self.evidences[id] = self.Evidence( - name, desc, image, pos, can_hide_in, show_in_dark, evi.triggers) + self.evidences[id] = self.Evidence(name, desc, image, pos, can_hide_in, show_in_dark, evi.triggers) new_name = self.evidences[id].name else: # Are you serious? This is absolutely fucking mental. diff --git a/server/hub_manager.py b/server/hub_manager.py index 006393b9..36b37700 100644 --- a/server/hub_manager.py +++ b/server/hub_manager.py @@ -24,16 +24,13 @@ def load(self, path="config/areas.yaml", hub_id=-1): with open(path, "r", encoding="utf-8") as stream: hubs = yaml.safe_load(stream) except Exception: - raise AreaError( - f"Trying to load Hub list: File path {path} is invalid!") + raise AreaError(f"Trying to load Hub list: File path {path} is invalid!") if hub_id != -1: try: self.hubs[hub_id].load(hubs[hub_id], destructive=True) except ValueError: - raise AreaError( - f"Invalid Hub ID {hub_id}! Please contact the server host." - ) + raise AreaError(f"Invalid Hub ID {hub_id}! Please contact the server host.") return if "area" in hubs[0]: @@ -51,13 +48,9 @@ def load(self, path="config/areas.yaml", hub_id=-1): # I hate this for a_name in reachable_areas: a_name = a_name.strip() - target_area = self.hubs[0].get_area_by_name( - a_name, case_insensitive=True - ) + target_area = self.hubs[0].get_area_by_name(a_name, case_insensitive=True) self.hubs[0].areas[i].link(target_area.id) - print( - f"[tsuDR conversion] Linking area {self.hubs[0].areas[i].name} to {target_area.name}" - ) + print(f"[tsuDR conversion] Linking area {self.hubs[0].areas[i].name} to {target_area.name}") is_dr_hub = True if "default_description" in area: self.hubs[0].areas[i].desc = area["default_description"] @@ -95,8 +88,7 @@ def save(self, path="config/areas.yaml"): hubs.append(hub.save()) yaml.dump(hubs, stream, default_flow_style=False) except Exception: - raise AreaError( - f"Trying to save Hub list: File path {path} is invalid!") + raise AreaError(f"Trying to save Hub list: File path {path} is invalid!") def default_hub(self): """Get the default hub.""" diff --git a/server/logger.py b/server/logger.py index a2a0e083..97af243d 100644 --- a/server/logger.py +++ b/server/logger.py @@ -4,8 +4,7 @@ def setup_logging(debug: bool = False): - formatter = logging.Formatter( - "[%(asctime)s][%(name)s][%(levelname)s] %(message)s") + formatter = logging.Formatter("[%(asctime)s][%(name)s][%(levelname)s] %(message)s") # Log to terminal (stdout) stdout_handler = logging.StreamHandler(sys.stdout) @@ -13,9 +12,7 @@ def setup_logging(debug: bool = False): logging.getLogger().addHandler(stdout_handler) # Log to server.log - serverlog_handler = logging.handlers.RotatingFileHandler( - "logs/server.log", encoding="utf-8", maxBytes=1024 * 512 - ) + serverlog_handler = logging.handlers.RotatingFileHandler("logs/server.log", encoding="utf-8", maxBytes=1024 * 512) # The serverlog should never log debug messages serverlog_handler.setLevel(logging.INFO) serverlog_handler.setFormatter(formatter) diff --git a/server/network/aoprotocol_ws.py b/server/network/aoprotocol_ws.py index 99bbc084..4a8603a5 100644 --- a/server/network/aoprotocol_ws.py +++ b/server/network/aoprotocol_ws.py @@ -22,11 +22,10 @@ def get_extra_info(self, key): """ remote_address = self.ws.remote_address - if (remote_address[0] == "127.0.0.1"): + if remote_address[0] == "127.0.0.1": # See if proxy try: - remote_address = ( - self.ws.request_headers['X-Forwarded-For'], 0) + remote_address = (self.ws.request_headers["X-Forwarded-For"], 0) except Exception: pass info = {"peername": remote_address} diff --git a/server/network/masterserverclient.py b/server/network/masterserverclient.py index 1ce0ac09..2f594660 100644 --- a/server/network/masterserverclient.py +++ b/server/network/masterserverclient.py @@ -6,15 +6,14 @@ import aiohttp import stun - logger = logging.getLogger("debug") stun_servers = [ - ('stun.l.google.com', 19302), - ('global.stun.twilio.com', 3478), - ('stun.voip.blackberry.com', 3478), + ("stun.l.google.com", 19302), + ("global.stun.twilio.com", 3478), + ("stun.voip.blackberry.com", 3478), ] -API_BASE_URL = 'https://servers.aceattorneyonline.com' +API_BASE_URL = "https://servers.aceattorneyonline.com" class MasterServerClient: @@ -48,7 +47,7 @@ async def connect(self): await self.send_server_info(http) except aiohttp.ClientError: # Masterserver is down or unreachable, may be temporary so log it as a warning - logger.warning('Failed to connect to the master server') + logger.warning("Failed to connect to the master server") except Exception: # Unknown error occurred, log it as a hard error with full exception information exc_type, exc_value, exc_traceback = sys.exc_info() @@ -65,8 +64,7 @@ def get_my_ip(self): str: The external IP address. """ for stun_ip, stun_port in stun_servers: - nat_type, external_ip, _external_port = \ - stun.get_ip_info(stun_host=stun_ip, stun_port=stun_port) + nat_type, external_ip, _external_port = stun.get_ip_info(stun_host=stun_ip, stun_port=stun_port) if nat_type != stun.Blocked: return external_ip @@ -86,7 +84,7 @@ async def send_server_info(self, http: aiohttp.ClientSession): cfg = self.server.config # Try to get the custom hostname - f_ip = cfg.get('masterserver_custom_hostname') + f_ip = cfg.get("masterserver_custom_hostname") # If fails, try to get the external IP if not f_ip: @@ -94,44 +92,44 @@ async def send_server_info(self, http: aiohttp.ClientSession): f_ip = await loop.run_in_executor(None, self.get_my_ip) advertise_body = { - 'ip': f_ip, - 'port': cfg['port'], - 'name': cfg['masterserver_name'], - 'description': cfg['masterserver_description'], - 'players': self.server.player_count + "ip": f_ip, + "port": cfg["port"], + "name": cfg["masterserver_name"], + "description": cfg["masterserver_description"], + "players": self.server.player_count, } self.add_ws_info(advertise_body) - async with http.post(f'{API_BASE_URL}/servers', json=advertise_body) as res: + async with http.post(f"{API_BASE_URL}/servers", json=advertise_body) as res: err_body = await res.text() try: res.raise_for_status() except aiohttp.ClientResponseError as err: logging.error("Got status=%s advertising %s: %s", err.status, advertise_body, err_body) - logger.debug('Heartbeat to %s/servers', API_BASE_URL) + logger.debug("Heartbeat to %s/servers", API_BASE_URL) # Helper to add websocket info to advertise_body def add_ws_info(self, advertise_body: dict) -> None: cfg = self.server.config - if 'use_websockets' not in cfg or not cfg['use_websockets']: + if "use_websockets" not in cfg or not cfg["use_websockets"]: # Explicitly disabled, return return - if 'websocket_port' not in cfg or not cfg['websocket_port']: + if "websocket_port" not in cfg or not cfg["websocket_port"]: # If we don't listen on a websocket port, don't advertise it return - ws_port = cfg['websocket_port'] + ws_port = cfg["websocket_port"] # Override if advertised_websocket_port is present and valid - if 'advertised_websocket_port' in cfg and cfg['advertised_websocket_port']: - ws_port = cfg['advertised_websocket_port'] + if "advertised_websocket_port" in cfg and cfg["advertised_websocket_port"]: + ws_port = cfg["advertised_websocket_port"] - advertise_body['ws_port'] = ws_port + advertise_body["ws_port"] = ws_port - if 'use_securewebsockets' in cfg and cfg['use_securewebsockets']: - if 'secure_websocket_port' in cfg and cfg['secure_websocket_port']: - advertise_body['wss_port'] = cfg['secure_websocket_port'] + if "use_securewebsockets" in cfg and cfg["use_securewebsockets"]: + if "secure_websocket_port" in cfg and cfg["secure_websocket_port"]: + advertise_body["wss_port"] = cfg["secure_websocket_port"] diff --git a/server/network/webhooks.py b/server/network/webhooks.py index e55b17b0..cdbf954f 100644 --- a/server/network/webhooks.py +++ b/server/network/webhooks.py @@ -43,9 +43,7 @@ def send_webhook( embed["title"] = title embed["color"] = color data["embeds"].append(embed) - result = requests.post( - url, data=json.dumps(data), headers={"Content-Type": "application/json"} - ) + result = requests.post(url, data=json.dumps(data), headers={"Content-Type": "application/json"}) try: result.raise_for_status() except requests.exceptions.HTTPError as err: @@ -53,9 +51,7 @@ def send_webhook( else: database.log_misc( "webhook.ok", - data="successfully delivered payload, code {}".format( - result.status_code - ), + data="successfully delivered payload, code {}".format(result.status_code), ) def modcall(self, char, ipid, area, reason=None): @@ -97,19 +93,18 @@ def login(self, client=None, loginprofile=""): is_enabled = self.server.config["login_webhook"]["enabled"] username = self.server.config["login_webhook"]["username"] avatar_url = self.server.config["login_webhook"]["avatar_url"] - + if not is_enabled: return message = f"{client.name} ({client.ipid}) (hdid: {client.hdid}) has logged in as mod profile: {loginprofile}" - self.send_webhook(username=username, - avatar_url=avatar_url, message=message) + self.send_webhook(username=username, avatar_url=avatar_url, message=message) def needcall(self, client=None, reason=None): if "need_webhook" not in self.server.config: return - + is_enabled = self.server.config["need_webhook"]["enabled"] username = self.server.config["need_webhook"]["username"] avatar_url = self.server.config["need_webhook"]["avatar_url"] @@ -150,19 +145,10 @@ def kick(self, ipid, reason="", client=None, char=None): message = f"{char} ({ipid})" if char is not None else str(ipid) message += " was kicked" - message += ( - f" by {client.name} ({client.ipid})" - if client is not None - else " from the server" - ) - message += ( - f" with reason: {reason}" - if reason.strip() != "" - else " (no reason provided)." - ) + message += f" by {client.name} ({client.ipid})" if client is not None else " from the server" + message += f" with reason: {reason}" if reason.strip() != "" else " (no reason provided)." - self.send_webhook(username=username, - avatar_url=avatar_url, message=message) + self.send_webhook(username=username, avatar_url=avatar_url, message=message) def ban( self, @@ -181,26 +167,13 @@ def ban( if not is_enabled: return message = f"{char} ({ipid})" if char is not None else str(ipid) - message += ( - f" (hdid: {hdid}) was hardware-banned" - if hdid is not None - else " was banned" - ) - message += ( - f" by {client.name} ({client.ipid})" - if client is not None - else " from the server" - ) + message += f" (hdid: {hdid}) was hardware-banned" if hdid is not None else " was banned" + message += f" by {client.name} ({client.ipid})" if client is not None else " from the server" message += f" with reason: {reason}" if reason.strip() != "" else "" message += f" (Ban ID: {ban_id})." - message += ( - f" It will expire {unban_date}" - if unban_date is not None - else " It is a permanent ban." - ) + message += f" It will expire {unban_date}" if unban_date is not None else " It is a permanent ban." - self.send_webhook(username=username, - avatar_url=avatar_url, message=message) + self.send_webhook(username=username, avatar_url=avatar_url, message=message) def unban(self, ban_id, client=None): is_enabled = self.server.config["unban_webhook"]["enabled"] @@ -211,11 +184,6 @@ def unban(self, ban_id, client=None): return message = f"Ban ID {ban_id} was revoked" - message += ( - f" by {client.name} ({client.ipid})." - if client is not None - else " by the server." - ) + message += f" by {client.name} ({client.ipid})." if client is not None else " by the server." - self.send_webhook(username=username, - avatar_url=avatar_url, message=message) + self.send_webhook(username=username, avatar_url=avatar_url, message=message) diff --git a/server/tsuserver.py b/server/tsuserver.py index 1abcfe19..5e4a32c3 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -21,7 +21,6 @@ from server.network.webhooks import Webhooks from server.constants import remove_URL, dezalgo - logger = logging.getLogger("main") @@ -74,8 +73,7 @@ def __init__(self): self.command_aliases = {} try: - self.geoIpReader = geoip2.database.Reader( - "./storage/GeoLite2-ASN.mmdb") + self.geoIpReader = geoip2.database.Reader("./storage/GeoLite2-ASN.mmdb") self.useGeoIp = True # on debian systems you can use /usr/share/GeoIP/GeoIPASNum.dat if the geoip-database-extra package is installed except FileNotFoundError: @@ -125,16 +123,11 @@ def start(self): if self.config["local"]: bound_ip = "127.0.0.1" - ao_server_crt = loop.create_server( - lambda: AOProtocol(self), bound_ip, self.config["port"] - ) + ao_server_crt = loop.create_server(lambda: AOProtocol(self), bound_ip, self.config["port"]) ao_server = loop.run_until_complete(ao_server_crt) if self.config["use_websockets"]: - ao_server_ws = websockets.serve( - new_websocket_client( - self), bound_ip, self.config["websocket_port"] - ) + ao_server_ws = websockets.serve(new_websocket_client(self), bound_ip, self.config["websocket_port"]) asyncio.ensure_future(ao_server_ws) if self.config["use_masterserver"]: @@ -152,9 +145,7 @@ def start(self): self.config["bridgebot"]["hub_id"], self.config["bridgebot"]["area_id"], ) - asyncio.ensure_future( - self.bridgebot.init(self.config["bridgebot"]["token"]), loop=loop - ) + asyncio.ensure_future(self.bridgebot.init(self.config["bridgebot"]["token"]), loop=loop) self.bridgebot.add_commands() except Exception as ex: # Don't end the whole server if bridgebot destroys itself @@ -162,12 +153,11 @@ def start(self): if "need_webhook" in self.config and self.config["need_webhook"]["enabled"]: self.need_webhook = True - + asyncio.ensure_future(self.schedule_unbans()) database.log_misc("start") - print("Server started and is listening on port {}".format( - self.config["port"])) + print("Server started and is listening on port {}".format(self.config["port"])) try: loop.run_forever() @@ -211,7 +201,10 @@ def new_client(self, transport): asn = "Loopback" for line, rangeBan in enumerate(self.ipRange_bans): - if rangeBan != "" and ((peername.startswith(rangeBan) and (rangeBan.endswith('.') or rangeBan.endswith(':'))) or asn == rangeBan): + if rangeBan != "" and ( + (peername.startswith(rangeBan) and (rangeBan.endswith(".") or rangeBan.endswith(":"))) + or asn == rangeBan + ): msg = "BD#" msg += "Abuse\r\n" msg += f"ID: {line}\r\n" @@ -235,23 +228,15 @@ def remove_client(self, client): """ if client.area: area = client.area - if ( - not area.dark - and not area.force_sneak - and not client.sneaking - and not client.hidden - ): - area.broadcast_ooc( - f"[{client.id}] {client.showname} has disconnected.") + if not area.dark and not area.force_sneak and not client.sneaking and not client.hidden: + area.broadcast_ooc(f"[{client.id}] {client.showname} has disconnected.") area.remove_client(client) self.client_manager.remove_client(client) @property def player_count(self): """Get the number of non-spectating clients.""" - return len( - [client for client in self.client_manager.clients if client.char_id != -1] - ) + return len([client for client in self.client_manager.clients if client.char_id != -1]) def load_config(self): """Load the main server configuration from a YAML file.""" @@ -288,8 +273,7 @@ def load_config(self): self.config["zalgo_tolerance"] = 3 if isinstance(self.config["modpass"], str): - self.config["modpass"] = {"default": { - "password": self.config["modpass"]}} + self.config["modpass"] = {"default": {"password": self.config["modpass"]}} if "multiclient_limit" not in self.config: self.config["multiclient_limit"] = 16 if "asset_url" not in self.config: @@ -306,9 +290,7 @@ def load_config(self): def load_command_aliases(self): """Load a list of alternative command names.""" try: - with open( - "config/command_aliases.yaml", "r", encoding="utf-8" - ) as command_aliases: + with open("config/command_aliases.yaml", "r", encoding="utf-8") as command_aliases: self.command_aliases = yaml.safe_load(command_aliases) except Exception: logger.debug("Cannot find command_aliases.yaml") @@ -450,11 +432,8 @@ def broadcast_global(self, client, msg, as_mod=False): as_mod = "[M]" else: as_mod = "" - ooc_name = ( - f"<dollar>G[{client.area.area_manager.abbreviation}]|{as_mod}{client.name}" - ) - self.send_all_cmd_pred("CT", ooc_name, msg, - pred=lambda x: not x.muted_global) + ooc_name = f"<dollar>G[{client.area.area_manager.abbreviation}]|{as_mod}{client.name}" + self.send_all_cmd_pred("CT", ooc_name, msg, pred=lambda x: not x.muted_global) def send_modchat(self, client, msg): """ @@ -463,8 +442,7 @@ def send_modchat(self, client, msg): :param msg: message """ - ooc_name = "{}[{}][{}]".format( - "<dollar>M", client.area.id, client.name) + ooc_name = "{}[{}][{}]".format("<dollar>M", client.area.id, client.name) self.send_all_cmd_pred("CT", ooc_name, msg, pred=lambda x: x.is_mod) def broadcast_need(self, client, msg): @@ -521,8 +499,7 @@ def send_arup(self, client, args): def send_discord_chat(self, name, message, hub_id=0, area_id=0): area = self.hub_manager.get_hub_by_id(hub_id).get_area_by_id(area_id) - area.area_manager.get_char_id_by_name( - self.config["bridgebot"]["character"]) + area.area_manager.get_char_id_by_name(self.config["bridgebot"]["character"]) message = dezalgo(message) message = remove_URL(message) message = ( @@ -567,12 +544,9 @@ def refresh(self): # Reload moderator passwords list and unmod any moderator affected by # credential changes or removals if isinstance(self.config["modpass"], str): - self.config["modpass"] = { - "default": {"password": self.config["modpass"]} - } + self.config["modpass"] = {"default": {"password": self.config["modpass"]}} if isinstance(cfg_yaml["modpass"], str): - cfg_yaml["modpass"] = {"default": { - "password": cfg_yaml["modpass"]}} + cfg_yaml["modpass"] = {"default": {"password": cfg_yaml["modpass"]}} for profile in self.config["modpass"]: if ( @@ -586,8 +560,7 @@ def refresh(self): client.is_mod = False client.mod_profile_name = None database.log_misc("unmod.modpass", client) - client.send_ooc( - "Your moderator credentials have been revoked.") + client.send_ooc("Your moderator credentials have been revoked.") self.config["modpass"] = cfg_yaml["modpass"] self.load_config() From 4e5edb85325021a107df06650f471a879288b52d Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 12:51:18 +0100 Subject: [PATCH 10/19] delete unused travis file --- .travis.yml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e01b74c2..00000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: python -python: - - "3.11" -stages: - - execute - -install: - - pip install -r requirements.txt - -jobs: - include: - - stage: execute - name: "Starting the Server" - script: python start_server.py \ No newline at end of file From 105224a0aa703cd7e3957026e2a79f2015704853 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 12:57:55 +0100 Subject: [PATCH 11/19] cool syntax bro --- .github/workflows/{pythonapp.yml => ruff-check.yml} | 0 server/constants.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename .github/workflows/{pythonapp.yml => ruff-check.yml} (100%) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/ruff-check.yml similarity index 100% rename from .github/workflows/pythonapp.yml rename to .github/workflows/ruff-check.yml diff --git a/server/constants.py b/server/constants.py index 68109e6f..af10aab2 100644 --- a/server/constants.py +++ b/server/constants.py @@ -22,10 +22,10 @@ class MusicEffect(IntFlag): class ReportCardReason(IntFlag): - Nothing = (0,) - Blackout = (1,) - PendingLook = (2,) - Blinded = (3,) + Nothing = 0 + Blackout = 1 + PendingLook = 2 + Blinded = 3 NoPlayerList = 4 From c0a785dadc739b15d3036eb2bc1886877b5b1c82 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:03:48 +0100 Subject: [PATCH 12/19] Deduplicate encode_ao_packet with inline escape helper Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --- server/constants.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/server/constants.py b/server/constants.py index af10aab2..c4fba3ac 100644 --- a/server/constants.py +++ b/server/constants.py @@ -85,23 +85,15 @@ def contains_URL(sample): def encode_ao_packet(params): + def escape(s): + return str(s).replace("#", "<num>").replace("%", "<percent>").replace("$", "<dollar>").replace("&", "<and>") + new_params = [] for arg in params: if type(arg) is tuple: - encoded = [] - for tup in arg: - encoded.append( - str(tup) - .replace("#", "<num>") - .replace("%", "<percent>") - .replace("$", "<dollar>") - .replace("&", "<and>") - ) - new_params.append(tuple(encoded)) + new_params.append(tuple(escape(tup) for tup in arg)) else: - new_params.append( - str(arg).replace("#", "<num>").replace("%", "<percent>").replace("$", "<dollar>").replace("&", "<and>") - ) + new_params.append(escape(arg)) return new_params From 9cde14cf299d4387c117068969b6f4d0b9637fca Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:11:08 +0100 Subject: [PATCH 13/19] Rename TsuServer3 to KFOServer - Rename tsuserver.py to kfoserver.py - Rename class TsuServer3 to KFOServer - Rename workflow to python-ci.yml and add test step - Update docker-compose service name - Replace tsuserver references with KFO-Server in docs and strings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --- .github/workflows/{ruff-check.yml => python-ci.yml} | 4 +++- README.md | 8 ++++---- docker-compose.yml | 4 ++-- server/client_manager.py | 2 +- server/commands/admin.py | 2 +- server/{tsuserver.py => kfoserver.py} | 4 ++-- server/network/webhooks.py | 2 +- start_server.py | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) rename .github/workflows/{ruff-check.yml => python-ci.yml} (89%) rename server/{tsuserver.py => kfoserver.py} (99%) diff --git a/.github/workflows/ruff-check.yml b/.github/workflows/python-ci.yml similarity index 89% rename from .github/workflows/ruff-check.yml rename to .github/workflows/python-ci.yml index 7f54b176..8e115dda 100644 --- a/.github/workflows/ruff-check.yml +++ b/.github/workflows/python-ci.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python lint +name: Python CI on: [push, fork, pull_request] @@ -20,3 +20,5 @@ jobs: run: uv sync - name: Lint with ruff run: uv run ruff check . + - name: Run tests + run: uv run pytest diff --git a/README.md b/README.md index c5c073fb..9e3d7a51 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,13 @@ uv sync This will create a virtual environment and install all dependencies. -### Configure tsuserver +### Configure KFO-Server * Copy `config_sample` to `config` * Edit the values in the `.yaml` files to your liking. * Be sure to check your YAML file for syntax errors. Use this website: <http://www.yamllint.com/> * *Use spaces only; do not use tabs.* -* You don't need to copy characters into the `characters` folder *unless* you specifically chose to disable iniswapping in an area (in `areas.yaml`). In this case, all tsuserver needs to know is the `char.ini` of each character. It doesn't need sprites. +* You don't need to copy characters into the `characters` folder *unless* you specifically chose to disable iniswapping in an area (in `areas.yaml`). In this case, all KFO-Server needs to know is the `char.ini` of each character. It doesn't need sprites. ### Run @@ -79,7 +79,7 @@ To stop the server, press Ctrl+C in the terminal. You can also use docker to run KFO-server. First you need to install [Docker](https://get.docker.com/) and [Docker Compose](https://docs.docker.com/compose/install/). -Once you have everything configured, do `docker-compose up`. It will build the image and start tsuserver up for you. If you accidentally restart the server, the container will automatically start back up. If you're not understanding why it's starting, try starting it up manually: +Once you have everything configured, do `docker-compose up`. It will build the image and start KFO-Server up for you. If you accidentally restart the server, the container will automatically start back up. If you're not understanding why it's starting, try starting it up manually: ## Pro Tips @@ -91,6 +91,6 @@ Once you have everything configured, do `docker-compose up`. It will build the i ## License -This server is licensed under the AGPLv3 license. In short, if you use a modified version of tsuserver3, you *must* distribute its source licensed under the AGPLv3 as well, and notify your users where the modified source may be found. The main difference between the AGPL and the GPL is that for the AGPL, network use counts as distribution. If you do not accept these terms, you should use [serverD](https://github.com/Attorney-Online-Engineering-Task-Force/serverD), which uses GPL rather than AGPL. +This server is licensed under the AGPLv3 license. In short, if you use a modified version of KFO-Server, you *must* distribute its source licensed under the AGPLv3 as well, and notify your users where the modified source may be found. The main difference between the AGPL and the GPL is that for the AGPL, network use counts as distribution. If you do not accept these terms, you should use [serverD](https://github.com/Attorney-Online-Engineering-Task-Force/serverD), which uses GPL rather than AGPL. See the [LICENSE](LICENSE.md) file for more information. diff --git a/docker-compose.yml b/docker-compose.yml index 6dce6734..5149935e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ version: "3.3" services: - tsuserver: + kfo-server: build: . - image: tsuserver + image: kfo-server ports: - 27016:27016 - 50001:50001 diff --git a/server/client_manager.py b/server/client_manager.py index 3dc83c57..3e99eca0 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -802,7 +802,7 @@ def clear_music(self): def load_music(self, path): """Load a music list from a path. Use it for the local music list and reload it.""" - # TODO: Move the musiclist parsing function to tsuserver3.py or something + # TODO: Move the musiclist parsing function to kfoserver.py or something try: with open(path, "r", encoding="utf-8") as stream: music_list = yaml.safe_load(stream) diff --git a/server/commands/admin.py b/server/commands/admin.py index a8c8fa6e..86a0bf55 100644 --- a/server/commands/admin.py +++ b/server/commands/admin.py @@ -55,7 +55,7 @@ def ooc_cmd_help(client, arg): if arg == "": msg = inspect.cleandoc(""" - Welcome to tsuserver3! You can use /help <command> on any known + Welcome to KFO-Server! You can use /help <command> on any known command to get up-to-date help on it. You may also use /help <category> to see available commands for that category. diff --git a/server/tsuserver.py b/server/kfoserver.py similarity index 99% rename from server/tsuserver.py rename to server/kfoserver.py index 5e4a32c3..71331fe7 100644 --- a/server/tsuserver.py +++ b/server/kfoserver.py @@ -24,8 +24,8 @@ logger = logging.getLogger("main") -class TsuServer3: - """The main class for KFO-Server derivative of tsuserver3 server software.""" +class KFOServer: + """The main class for KFO-Server.""" def __init__(self): self.software = "KFO-Server" diff --git a/server/network/webhooks.py b/server/network/webhooks.py index cdbf954f..e85f6aaa 100644 --- a/server/network/webhooks.py +++ b/server/network/webhooks.py @@ -35,7 +35,7 @@ def send_webhook( data = {} data["content"] = message data["avatar_url"] = avatar_url - data["username"] = username if username is not None else "tsuserver webhook" + data["username"] = username if username is not None else "KFO-Server webhook" if embed is True: data["embeds"] = [] embed = {} diff --git a/start_server.py b/start_server.py index 4876317c..b2e0e06b 100644 --- a/start_server.py +++ b/start_server.py @@ -1,10 +1,10 @@ import os -from server.tsuserver import TsuServer3 +from server.kfoserver import KFOServer def main(): - server = TsuServer3() + server = KFOServer() server.start() From 1aa2ce1fee9e751f98a08ce7bb529d0eac5f1bd4 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:20:20 +0100 Subject: [PATCH 14/19] Auto-copy config_sample to config on startup - Copy config_sample to config if config folder doesn't exist - Remove hatchling build backend (not needed for server app) - Remove deprecated docker-compose version field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --- docker-compose.yml | 1 - pyproject.toml | 7 ------- start_server.py | 7 ++++++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5149935e..150be54d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.3" services: kfo-server: build: . diff --git a/pyproject.toml b/pyproject.toml index 2b9d579e..73888541 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,6 @@ dependencies = [ "websockets>=12.0", ] -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["server"] - [tool.black] line-length = 120 target-version = ["py311"] diff --git a/start_server.py b/start_server.py index b2e0e06b..1e148971 100644 --- a/start_server.py +++ b/start_server.py @@ -1,15 +1,20 @@ import os +import shutil from server.kfoserver import KFOServer def main(): + if not os.path.exists("config"): + print("Config folder not found, copying from config_sample...") + shutil.copytree("config_sample", "config") + server = KFOServer() server.start() if __name__ == "__main__": - print("KFO-server - an Attorney Online server") + print("KFO-Server - an Attorney Online server") try: main() except KeyboardInterrupt: From b610ce37fb4dd68e24f3778d544a875ac743a205 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:20:47 +0100 Subject: [PATCH 15/19] default to false --- config_sample/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config_sample/config.yaml b/config_sample/config.yaml index 6ee25318..70f4052e 100644 --- a/config_sample/config.yaml +++ b/config_sample/config.yaml @@ -48,14 +48,14 @@ websocket_port: 50001 # Whether the server is open to secure websocket connections use_securewebsockets: false -# Port that is advertised to the masterserver for WSS connections (KFO-Server does not accept SSL directly) +# Port advertised to the masterserver for WSS connections (KFO-Server does not accept SSL directly) secure_websocket_port: 443 # WebAO Asset URL for hosting files. Leave blank to use vanilla asset_url: # Whether the server should be advertised on the server list. -use_masterserver: true +use_masterserver: false # How the server should be listed on the server list. masterserver_name: My First Server masterserver_description: This is my flashy new server From 3743f3f67001643c98a46c24a23642c38f501312 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:31:00 +0100 Subject: [PATCH 16/19] Fix asyncio compatibility for Python 3.10+ - Wrap websockets.serve() in async function to avoid get_running_loop() error - Update websocket handler to new single-argument API - Replace deprecated asyncio.get_event_loop() with asyncio.to_thread() - Remove deprecated loop= parameter from ensure_future calls Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --- server/kfoserver.py | 10 ++++++---- server/network/aoprotocol_ws.py | 2 +- server/network/masterserverclient.py | 3 +-- uv.lock | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/kfoserver.py b/server/kfoserver.py index 71331fe7..ed366fc1 100644 --- a/server/kfoserver.py +++ b/server/kfoserver.py @@ -126,13 +126,15 @@ def start(self): ao_server_crt = loop.create_server(lambda: AOProtocol(self), bound_ip, self.config["port"]) ao_server = loop.run_until_complete(ao_server_crt) + async def start_websockets(): + await websockets.serve(new_websocket_client(self), bound_ip, self.config["websocket_port"]) + if self.config["use_websockets"]: - ao_server_ws = websockets.serve(new_websocket_client(self), bound_ip, self.config["websocket_port"]) - asyncio.ensure_future(ao_server_ws) + loop.run_until_complete(start_websockets()) if self.config["use_masterserver"]: self.ms_client = MasterServerClient(self) - asyncio.ensure_future(self.ms_client.connect(), loop=loop) + asyncio.ensure_future(self.ms_client.connect()) if self.config["zalgo_tolerance"]: self.zalgo_tolerance = self.config["zalgo_tolerance"] @@ -145,7 +147,7 @@ def start(self): self.config["bridgebot"]["hub_id"], self.config["bridgebot"]["area_id"], ) - asyncio.ensure_future(self.bridgebot.init(self.config["bridgebot"]["token"]), loop=loop) + asyncio.ensure_future(self.bridgebot.init(self.config["bridgebot"]["token"])) self.bridgebot.add_commands() except Exception as ex: # Don't end the whole server if bridgebot destroys itself diff --git a/server/network/aoprotocol_ws.py b/server/network/aoprotocol_ws.py index 4a8603a5..e62ef6c8 100644 --- a/server/network/aoprotocol_ws.py +++ b/server/network/aoprotocol_ws.py @@ -82,7 +82,7 @@ def new_websocket_client(server): """ - async def func(websocket, _): + async def func(websocket): client = AOProtocolWS(server, websocket) while client.ws_connected: await client.ws_handle() diff --git a/server/network/masterserverclient.py b/server/network/masterserverclient.py index 2f594660..2d9cf348 100644 --- a/server/network/masterserverclient.py +++ b/server/network/masterserverclient.py @@ -88,8 +88,7 @@ async def send_server_info(self, http: aiohttp.ClientSession): # If fails, try to get the external IP if not f_ip: - loop = asyncio.get_event_loop() - f_ip = await loop.run_in_executor(None, self.get_my_ip) + f_ip = await asyncio.to_thread(self.get_my_ip) advertise_body = { "ip": f_ip, diff --git a/uv.lock b/uv.lock index 37e489b1..3066b4e9 100644 --- a/uv.lock +++ b/uv.lock @@ -537,7 +537,7 @@ wheels = [ [[package]] name = "kfo-server" version = "0.1.0" -source = { editable = "." } +source = { virtual = "." } dependencies = [ { name = "aiohttp" }, { name = "arrow" }, From 66dfa059bfec191362e27c29144a15819acebdec Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 13:51:33 +0100 Subject: [PATCH 17/19] setuptools --- pyproject.toml | 7 +++++++ uv.lock | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 73888541..9e6146ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,13 @@ dependencies = [ "websockets>=12.0", ] +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["server"] + [tool.black] line-length = 120 target-version = ["py311"] diff --git a/uv.lock b/uv.lock index 3066b4e9..37e489b1 100644 --- a/uv.lock +++ b/uv.lock @@ -537,7 +537,7 @@ wheels = [ [[package]] name = "kfo-server" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "arrow" }, From 830b8b2d858296f62faf3c5064ba6704b1eb1a14 Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 14:55:08 +0100 Subject: [PATCH 18/19] use ruff format instead --- README.md | 24 +++++++----- pyproject.toml | 11 +++--- uv.lock | 103 ------------------------------------------------- 3 files changed, 21 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 9e3d7a51..fbec2249 100644 --- a/README.md +++ b/README.md @@ -9,28 +9,34 @@ If you are looking to use KFO-Server to set up a server, see [Server setup](#ser To set up KFO-Server for development, follow these instructions. -### Install dependencies +First, install dependencies: ```bash uv sync ``` -### Run tests +Then you can run the server using uv: ```bash -uv run tox +uv run python start_server.py ``` -### Format code +### Run linter -```bash -uv run black . +```(bash) +uv run ruff check . +``` + +### Run tests + +```(bash) +uv run tox ``` -### Lint code +### Run formatter -```bash -uv run ruff check . +```(bash) +uv run ruff format . ``` ## Server setup diff --git a/pyproject.toml b/pyproject.toml index 9e6146ff..73f92a46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ requires-python = ">=3.11" dependencies = [ "aiohttp>=3.13.2", "arrow>=1.3.0", - "black", "discord.py>=2.5.2", "ruff", "geoip2>=4.7.0", @@ -28,15 +27,17 @@ build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["server"] -[tool.black] -line-length = 120 -target-version = ["py311"] - [tool.ruff] line-length = 120 +target-version = "py311" # Exclude utility scripts from linting exclude = ["scripts/"] +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false + [tool.ruff.lint] # E741: Ambiguous variable name (e.g., 'l') - used intentionally in list comprehensions ignore = ["E741"] diff --git a/uv.lock b/uv.lock index 37e489b1..102ab2e9 100644 --- a/uv.lock +++ b/uv.lock @@ -208,43 +208,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, ] -[[package]] -name = "black" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, - { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, - { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, - { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, - { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, - { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, - { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, - { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, - { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, -] - [[package]] name = "cachetools" version = "6.2.4" @@ -345,18 +308,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -541,7 +492,6 @@ source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "arrow" }, - { name = "black" }, { name = "discord-py" }, { name = "geoip2" }, { name = "oyaml" }, @@ -559,7 +509,6 @@ dependencies = [ requires-dist = [ { name = "aiohttp", specifier = ">=3.13.2" }, { name = "arrow", specifier = ">=1.3.0" }, - { name = "black" }, { name = "discord-py", specifier = ">=2.5.2" }, { name = "geoip2", specifier = ">=4.7.0" }, { name = "oyaml", specifier = ">=1.0" }, @@ -761,15 +710,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "oyaml" version = "1.0" @@ -791,15 +731,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] -[[package]] -name = "pathspec" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, -] - [[package]] name = "platformdirs" version = "4.5.1" @@ -975,40 +906,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "pytokens" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/16/4b9cfd90d55e66ffdb277d7ebe3bc25250c2311336ec3fc73b2673c794d5/pytokens-0.4.0.tar.gz", hash = "sha256:6b0b03e6ea7c9f9d47c5c61164b69ad30f4f0d70a5d9fe7eac4d19f24f77af2d", size = 15039, upload-time = "2026-01-19T07:59:50.623Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/05/3196399a353dd4cd99138a88f662810979ee2f1a1cdb0b417cb2f4507836/pytokens-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92eb3ef88f27c22dc9dbab966ace4d61f6826e02ba04dac8e2d65ea31df56c8e", size = 160075, upload-time = "2026-01-19T07:59:00.316Z" }, - { url = "https://files.pythonhosted.org/packages/28/1d/c8fc4ed0a1c4f660391b201cda00b1d5bbcc00e2998e8bcd48b15eefd708/pytokens-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4b77858a680635ee9904306f54b0ee4781effb89e211ba0a773d76539537165", size = 247318, upload-time = "2026-01-19T07:59:01.636Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0e/53e55ba01f3e858d229cd84b02481542f42ba59050483a78bf2447ee1af7/pytokens-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25cacc20c2ad90acb56f3739d87905473c54ca1fa5967ffcd675463fe965865e", size = 259752, upload-time = "2026-01-19T07:59:04.229Z" }, - { url = "https://files.pythonhosted.org/packages/dc/56/2d930d7f899e3f21868ca6e8ec739ac31e8fc532f66e09cbe45d3df0a84f/pytokens-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fab535ebc9079e4db35cd63cb401901c7ce8720a9834f9ad44b9eb4e0f1d4", size = 262842, upload-time = "2026-01-19T07:59:06.14Z" }, - { url = "https://files.pythonhosted.org/packages/42/dd/4e7e6920d23deffaf66e6f40d45f7610dcbc132ca5d90ab4faccef22f624/pytokens-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:4d0f568d7e82b7e96be56d03b5081de40e43c904eb6492bf09aaca47cd55f35b", size = 102620, upload-time = "2026-01-19T07:59:07.839Z" }, - { url = "https://files.pythonhosted.org/packages/3d/65/65460ebbfefd0bc1b160457904370d44f269e6e4582e0a9b6cba7c267b04/pytokens-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd8da894e5a29ba6b6da8be06a4f7589d7220c099b5e363cb0643234b9b38c2a", size = 159864, upload-time = "2026-01-19T07:59:08.908Z" }, - { url = "https://files.pythonhosted.org/packages/25/70/a46669ec55876c392036b4da9808b5c3b1c5870bbca3d4cc923bf68bdbc1/pytokens-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:237ba7cfb677dbd3b01b09860810aceb448871150566b93cd24501d5734a04b1", size = 254448, upload-time = "2026-01-19T07:59:10.594Z" }, - { url = "https://files.pythonhosted.org/packages/62/0b/c486fc61299c2fc3b7f88ee4e115d4c8b6ffd1a7f88dc94b398b5b1bc4b8/pytokens-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01d1a61e36812e4e971cfe2c0e4c1f2d66d8311031dac8bf168af8a249fa04dd", size = 268863, upload-time = "2026-01-19T07:59:12.31Z" }, - { url = "https://files.pythonhosted.org/packages/79/92/b036af846707d25feaff7cafbd5280f1bd6a1034c16bb06a7c910209c1ab/pytokens-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47e2ef3ec6ee86909e520d79f965f9b23389fda47460303cf715d510a6fe544", size = 267181, upload-time = "2026-01-19T07:59:13.856Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c0/6d011fc00fefa74ce34816c84a923d2dd7c46b8dbc6ee52d13419786834c/pytokens-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d36954aba4557fd5a418a03cf595ecbb1cdcce119f91a49b19ef09d691a22ae", size = 102814, upload-time = "2026-01-19T07:59:15.288Z" }, - { url = "https://files.pythonhosted.org/packages/98/63/627b7e71d557383da5a97f473ad50f8d9c2c1f55c7d3c2531a120c796f6e/pytokens-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73eff3bdd8ad08da679867992782568db0529b887bed4c85694f84cdf35eafc6", size = 159744, upload-time = "2026-01-19T07:59:16.88Z" }, - { url = "https://files.pythonhosted.org/packages/28/d7/16f434c37ec3824eba6bcb6e798e5381a8dc83af7a1eda0f95c16fe3ade5/pytokens-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d97cc1f91b1a8e8ebccf31c367f28225699bea26592df27141deade771ed0afb", size = 253207, upload-time = "2026-01-19T07:59:18.069Z" }, - { url = "https://files.pythonhosted.org/packages/ab/96/04102856b9527701ae57d74a6393d1aca5bad18a1b1ca48ccffb3c93b392/pytokens-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c8952c537cb73a1a74369501a83b7f9d208c3cf92c41dd88a17814e68d48ce", size = 267452, upload-time = "2026-01-19T07:59:19.328Z" }, - { url = "https://files.pythonhosted.org/packages/0e/ef/0936eb472b89ab2d2c2c24bb81c50417e803fa89c731930d9fb01176fe9f/pytokens-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dbf56f3c748aed9310b310d5b8b14e2c96d3ad682ad5a943f381bdbbdddf753", size = 265965, upload-time = "2026-01-19T07:59:20.613Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f5/64f3d6f7df4a9e92ebda35ee85061f6260e16eac82df9396020eebbca775/pytokens-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:e131804513597f2dff2b18f9911d9b6276e21ef3699abeffc1c087c65a3d975e", size = 102813, upload-time = "2026-01-19T07:59:22.012Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f1/d07e6209f18ef378fc2ae9dee8d1dfe91fd2447c2e2dbfa32867b6dd30cf/pytokens-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0d7374c917197106d3c4761374718bc55ea2e9ac0fb94171588ef5840ee1f016", size = 159968, upload-time = "2026-01-19T07:59:23.07Z" }, - { url = "https://files.pythonhosted.org/packages/0a/73/0eb111400abd382a04f253b269819db9fcc748aa40748441cebdcb6d068f/pytokens-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cd3fa1caf9e47a72ee134a29ca6b5bea84712724bba165d6628baa190c6ea5b", size = 253373, upload-time = "2026-01-19T07:59:24.381Z" }, - { url = "https://files.pythonhosted.org/packages/bd/8d/9e4e2fdb5bcaba679e54afcc304e9f13f488eb4d626e6b613f9553e03dbd/pytokens-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c6986576b7b07fe9791854caa5347923005a80b079d45b63b0be70d50cce5f1", size = 267024, upload-time = "2026-01-19T07:59:25.74Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b7/e0a370321af2deb772cff14ff337e1140d1eac2c29a8876bfee995f486f0/pytokens-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9940f7c2e2f54fb1cb5fe17d0803c54da7a2bf62222704eb4217433664a186a7", size = 270912, upload-time = "2026-01-19T07:59:27.072Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/4348f916c440d4c3e68b53b4ed0e66b292d119e799fa07afa159566dcc86/pytokens-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:54691cf8f299e7efabcc25adb4ce715d3cef1491e1c930eaf555182f898ef66a", size = 103836, upload-time = "2026-01-19T07:59:28.112Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/a693c0cfa9c783a2a8c4500b7b2a8bab420f8ca4f2d496153226bf1c12e3/pytokens-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94ff5db97a0d3cd7248a5b07ba2167bd3edc1db92f76c6db00137bbaf068ddf8", size = 167643, upload-time = "2026-01-19T07:59:29.292Z" }, - { url = "https://files.pythonhosted.org/packages/c0/dd/a64eb1e9f3ec277b69b33ef1b40ffbcc8f0a3bafcde120997efc7bdefebf/pytokens-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0dd6261cd9cc95fae1227b1b6ebee023a5fd4a4b6330b071c73a516f5f59b63", size = 289553, upload-time = "2026-01-19T07:59:30.537Z" }, - { url = "https://files.pythonhosted.org/packages/df/22/06c1079d93dbc3bca5d013e1795f3d8b9ed6c87290acd6913c1c526a6bb2/pytokens-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdca8159df407dbd669145af4171a0d967006e0be25f3b520896bc7068f02c4", size = 302490, upload-time = "2026-01-19T07:59:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/8d/de/a6f5e43115b4fbf4b93aa87d6c83c79932cdb084f9711daae04549e1e4ad/pytokens-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4b5770abeb2a24347380a1164a558f0ebe06e98aedbd54c45f7929527a5fb26e", size = 305652, upload-time = "2026-01-19T07:59:33.685Z" }, - { url = "https://files.pythonhosted.org/packages/ab/3d/c136e057cb622e36e0c3ff7a8aaa19ff9720050c4078235691da885fe6ee/pytokens-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:74500d72c561dad14c037a9e86a657afd63e277dd5a3bb7570932ab7a3b12551", size = 115472, upload-time = "2026-01-19T07:59:34.734Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3c/6941a82f4f130af6e1c68c076b6789069ef10c04559bd4733650f902fd3b/pytokens-0.4.0-py3-none-any.whl", hash = "sha256:0508d11b4de157ee12063901603be87fb0253e8f4cb9305eb168b1202ab92068", size = 13224, upload-time = "2026-01-19T07:59:49.822Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" From ec237c5e477d807794e4bdf9e8ba6d959a60ea5e Mon Sep 17 00:00:00 2001 From: David Skoland <davidskoland@gmail.com> Date: Fri, 23 Jan 2026 14:58:04 +0100 Subject: [PATCH 19/19] format again --- server/area.py | 8 +- server/client_manager.py | 10 +- server/commands/admin.py | 4 +- server/commands/area_access.py | 2 +- server/commands/battle.py | 8 +- server/commands/casing.py | 16 +- server/commands/inventory.py | 4 +- server/commands/roleplay.py | 6 +- server/database.py | 4 +- server/emotes.py | 6 +- server/evidence.py | 12 +- server/network/aoprotocol.py | 629 +++++++++++++-------------------- 12 files changed, 295 insertions(+), 414 deletions(-) diff --git a/server/area.py b/server/area.py index ca4e198d..c4832a9d 100644 --- a/server/area.py +++ b/server/area.py @@ -57,7 +57,7 @@ def timer_expired(self): self.static = datetime.timedelta(0) self.started = False - self.area.broadcast_ooc(f"Timer {self.id+1} has expired.") + self.area.broadcast_ooc(f"Timer {self.id + 1} has expired.") self.call_commands() def call_commands(self): @@ -1044,11 +1044,11 @@ def send_ic( lst = list(self.testimony[idx]) lst[4] = "}}}" + msg[2:] self.testimony[idx] = tuple(lst) - self.broadcast_ooc(f"{client.showname} has amended Statement {idx+1}.") + self.broadcast_ooc(f"{client.showname} has amended Statement {idx + 1}.") if not self.recording: self.testimony_send(idx) except IndexError: - client.send_ooc(f"Something went wrong, couldn't amend Statement {idx+1}!") + client.send_ooc(f"Something went wrong, couldn't amend Statement {idx + 1}!") return adding = msg.strip() != "" and self.recording and client is not None @@ -1383,7 +1383,7 @@ def send_ic( # Add one statement ahead of the one we're currently on. idx += 1 self.testimony.insert(idx, args) - self.broadcast_ooc(f"Statement {idx+1} added.") + self.broadcast_ooc(f"Statement {idx + 1} added.") if not self.recording: self.testimony_send(idx) diff --git a/server/client_manager.py b/server/client_manager.py index 3e99eca0..f6793c27 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -1180,7 +1180,7 @@ def change_area(self, area, password=""): if old_area.area_manager == self.area.area_manager: if self.area.area_manager.passing_msg is True: old_area.send_ic( - msg=f'~~{"}}}"}[º{self.showname}º leaves to º{area.name}º.]', + msg=f"~~{'}}}'}[º{self.showname}º leaves to º{area.name}º.]", emote_mod=1, ) for c in old_area.clients: @@ -1226,7 +1226,7 @@ def change_area(self, area, password=""): ) if self.area.area_manager.passing_msg is True: self.area.send_ic( - msg=f'~~{"}}}"}[º{self.showname}º enters from º{old_area.name}º.]', + msg=f"~~{'}}}'}[º{self.showname}º enters from º{old_area.name}º.]", emote_mod=1, ) else: @@ -1524,7 +1524,11 @@ def get_area_clients(self, area_id, mods=False, afk_check=False, show_links=Fals else ( 4 if (x in area._owners) - else 5 if (x in area.area_manager.owners) else 6 if x.is_mod else 0 + else 5 + if (x in area.area_manager.owners) + else 6 + if x.is_mod + else 0 ) ) ) diff --git a/server/commands/admin.py b/server/commands/admin.py index 86a0bf55..f09b30a8 100644 --- a/server/commands/admin.py +++ b/server/commands/admin.py @@ -167,7 +167,7 @@ def kickban(client, arg, ban_hdid): raise ArgumentError("Invalid ban duration.") unban_date = arrow.get().shift(seconds=duration).datetime else: - raise ArgumentError(f"Ambiguous input: {arg}\nPlease wrap your arguments " "in quotes.") + raise ArgumentError(f"Ambiguous input: {arg}\nPlease wrap your arguments in quotes.") try: raw_ipid = args[0] @@ -394,7 +394,7 @@ def ooc_cmd_bans(client, _arg): msg = "Last 5 bans:\n" for ban in database.recent_bans(): time = arrow.get(ban.ban_date).humanize() - msg += f"{time}: {ban.banned_by_name} ({ban.banned_by}) issued ban " f"{ban.ban_id} ('{ban.reason}')\n" + msg += f"{time}: {ban.banned_by_name} ({ban.banned_by}) issued ban {ban.ban_id} ('{ban.reason}')\n" client.send_ooc(msg) diff --git a/server/commands/area_access.py b/server/commands/area_access.py index b2f7da51..2e33aaeb 100644 --- a/server/commands/area_access.py +++ b/server/commands/area_access.py @@ -700,7 +700,7 @@ def ooc_cmd_pw(client, arg): if password == "": if client.is_mod or client in client.area.owners: if link is not None and link["password"] != "": - client.send_ooc(f'Link {client.area.id}-{area.id} password is: {link["password"]}') + client.send_ooc(f"Link {client.area.id}-{area.id} password is: {link['password']}") else: client.send_ooc(f"Area [{area.id}] {area.name} password is: {area.password}") else: diff --git a/server/commands/battle.py b/server/commands/battle.py index e8a06d32..e52c455e 100644 --- a/server/commands/battle.py +++ b/server/commands/battle.py @@ -78,7 +78,7 @@ def send_info_fighter(client): msg = f"\n👤 {client.battle.fighter} 👤:\n" if client.battle.status is not None: msg += f"Status 🌈: {client.battle.status}\n" - msg += f"\nHP 💗: {round(client.battle.hp,2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana,2)}\nATK 🗡️: {round(client.battle.atk,2)}\nDEF 🛡️: {round(client.battle.defe,2)}\nSPA ✨: {round(client.battle.spa,2)}\nSPD 🔮: {round(client.battle.spd,2)}\nSPE 💨: {round(client.battle.spe,2)}\n\n" + msg += f"\nHP 💗: {round(client.battle.hp, 2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana, 2)}\nATK 🗡️: {round(client.battle.atk, 2)}\nDEF 🛡️: {round(client.battle.defe, 2)}\nSPA ✨: {round(client.battle.spa, 2)}\nSPD 🔮: {round(client.battle.spd, 2)}\nSPE 💨: {round(client.battle.spe, 2)}\n\n" for move in client.battle.moves: move_id = client.battle.moves.index(move) msg += f"🌠 [{move_id}]{move.name} 🌠:\nManaCost 💧: {move.cost}\nType 💠: {move.type}\nPower 💪: {move.power}\nAccuracy 🔎: {move.accuracy}%\n" @@ -97,7 +97,7 @@ def send_stats_fighter(client): msg = f"\n👤 {client.battle.fighter} 👤:\n" if client.battle.status is not None: msg += f"Status 🌈: {client.battle.status}\n" - msg += f"\nHP 💗: {round(client.battle.hp,2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana,2)}\nATK 🗡️: {round(client.battle.atk,2)}\nDEF 🛡️: {round(client.battle.defe,2)}\nSPA ✨: {round(client.battle.spa,2)}\nSPD 🔮: {round(client.battle.spd,2)}\nSPE 💨: {round(client.battle.spe,2)}\n\n" + msg += f"\nHP 💗: {round(client.battle.hp, 2)}/{client.battle.maxhp}\nMANA 💧: {round(client.battle.mana, 2)}\nATK 🗡️: {round(client.battle.atk, 2)}\nDEF 🛡️: {round(client.battle.defe, 2)}\nSPA ✨: {round(client.battle.spa, 2)}\nSPD 🔮: {round(client.battle.spd, 2)}\nSPE 💨: {round(client.battle.spe, 2)}\n\n" client.send_ooc(msg) @@ -409,7 +409,7 @@ def send_battle_info(client): emoji = "⚔️" if client.area.battle_show_hp: - show_hp = f": {round(client.battle.hp*100/client.battle.maxhp,2)}%" + show_hp = f": {round(client.battle.hp * 100 / client.battle.maxhp, 2)}%" else: show_hp = "" @@ -424,7 +424,7 @@ def send_battle_info(client): emoji = "⚔️" if client.area.battle_show_hp: - show_hp = f": {round(client.battle.hp*100/client.battle.maxhp,2)}%" + show_hp = f": {round(client.battle.hp * 100 / client.battle.maxhp, 2)}%" else: show_hp = "" diff --git a/server/commands/casing.py b/server/commands/casing.py index 2cbd66c8..396528e7 100644 --- a/server/commands/casing.py +++ b/server/commands/casing.py @@ -101,7 +101,7 @@ def ooc_cmd_evidence(client, arg): # 0 = name # 1 = desc # 2 = image - evi_msg = f"\n💼[{i+1}]: '{evi[0]}'" # (🖼️{evi[2]}) + evi_msg = f"\n💼[{i + 1}]: '{evi[0]}'" # (🖼️{evi[2]}) if arg == "" or arg.lower() in evi_msg.lower(): msg += evi_msg msg += "\n\n|| Use /evidence [evi_name/id] to read specific evidence. ||" @@ -117,7 +117,7 @@ def ooc_cmd_evidence(client, arg): break if evidence is None: raise AreaError(f"Target evidence not found! (/evidence {arg})") - msg = f"==💼[{i+1}]: '{evidence[0]}==" + msg = f"==💼[{i + 1}]: '{evidence[0]}==" msg += f"\n🖼️Image: {evidence[2]}" msg += f"\n📃Desc:\n{evidence[1]}" msg += "\n\n|| Use /evidence to read all evidence in the area ||" @@ -551,7 +551,7 @@ def ooc_cmd_testimony(client, arg): return idx = int(args[0]) - 1 client.area.testimony_send(idx) - client.area.broadcast_ooc(f"{client.showname} has moved to Statement {idx+1}.") + client.area.broadcast_ooc(f"{client.showname} has moved to Statement {idx + 1}.") except ValueError: raise ArgumentError("Index must be a number!") except ClientError: @@ -570,7 +570,7 @@ def ooc_cmd_testimony(client, arg): here = " " if i == client.area.testimony_index: here = " >" - msg += f"\n{here}{i+1}) {name}: {txt}" + msg += f"\n{here}{i + 1}) {name}: {txt}" client.send_ooc(msg) @@ -640,7 +640,7 @@ def ooc_cmd_testimony_remove(client, arg): client.area.testimony.pop(idx) if client.area.testimony_index == idx: client.area.testimony_index = -1 - client.area.broadcast_ooc(f"{client.showname} has removed Statement {idx+1}.") + client.area.broadcast_ooc(f"{client.showname} has removed Statement {idx + 1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -666,7 +666,7 @@ def ooc_cmd_testimony_amend(client, arg): lst = list(client.area.testimony[idx]) lst[4] = "}}}" + " ".join(args[1:]) client.area.testimony[idx] = tuple(lst) - client.area.broadcast_ooc(f"{client.showname} has amended Statement {idx+1}.") + client.area.broadcast_ooc(f"{client.showname} has amended Statement {idx + 1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -694,7 +694,7 @@ def ooc_cmd_testimony_swap(client, arg): client.area.testimony[idx1], client.area.testimony[idx2], ) - client.area.broadcast_ooc(f"{client.showname} has swapped Statements {idx1+1} and {idx2+1}.") + client.area.broadcast_ooc(f"{client.showname} has swapped Statements {idx1 + 1} and {idx2 + 1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: @@ -721,7 +721,7 @@ def ooc_cmd_testimony_insert(client, arg): statement = client.area.testimony.pop(idx1) client.area.testimony.insert(idx2, statement) - client.area.broadcast_ooc(f"{client.showname} has inserted Statement {idx1+1} into {idx2+1}.") + client.area.broadcast_ooc(f"{client.showname} has inserted Statement {idx1 + 1} into {idx2 + 1}.") except ValueError: raise ArgumentError("Index must be a number!") except IndexError: diff --git a/server/commands/inventory.py b/server/commands/inventory.py index 741683fa..b8816645 100644 --- a/server/commands/inventory.py +++ b/server/commands/inventory.py @@ -25,7 +25,7 @@ def get_inventory(evi_list, arg): # 0 = name # 1 = desc # 2 = image - evi_msg = f"\n💼[{i+1}]: '{evi[0]}'" # (🖼️{evi[2]}) + evi_msg = f"\n💼[{i + 1}]: '{evi[0]}'" # (🖼️{evi[2]}) if arg == "" or arg.lower() in evi_msg.lower(): msg += evi_msg msg += "\n\n|| Use /inventory [evi_name/id] to read specific evidence. ||" @@ -40,7 +40,7 @@ def get_inventory(evi_list, arg): break if evidence is None: raise AreaError(f"Target evidence not found! (/inventory {arg})") - msg = f"==💼[{i+1}]: '{evidence[0]}==" + msg = f"==💼[{i + 1}]: '{evidence[0]}==" msg += f"\n🖼️Image: {evidence[2]}" msg += f"\n📃Desc:\n{evidence[1]}" msg += f"\n\n|| Use /inventory_drop {i} to drop this into the area ||" diff --git a/server/commands/roleplay.py b/server/commands/roleplay.py index 59431334..0dfe5c00 100644 --- a/server/commands/roleplay.py +++ b/server/commands/roleplay.py @@ -512,7 +512,7 @@ def ooc_cmd_rps(client, arg): if not arg: msg = "RPS rules:" for i, rule in enumerate(rps_rules): - msg += f"\n  {i+1}) " + msg += f"\n  {i + 1}) " choice = rule[0] msg += choice if len(choice) > 1: @@ -690,9 +690,9 @@ def ooc_cmd_timer(client, arg): for timer_id, timer in enumerate(client.area.timers): if timer.set: if timer.started: - msg += f"\nTimer {timer_id+1} is at {timer.target - arrow.get()}" + msg += f"\nTimer {timer_id + 1} is at {timer.target - arrow.get()}" else: - msg += f"\nTimer {timer_id+1} is at {timer.static}" + msg += f"\nTimer {timer_id + 1} is at {timer.static}" client.send_ooc(msg) return # TI packet specification: diff --git a/server/database.py b/server/database.py index e5c10b61..52496d3e 100644 --- a/server/database.py +++ b/server/database.py @@ -204,7 +204,7 @@ def ban( (target_id, ban_id), ) except sqlite3.IntegrityError as exc: - raise ServerError(f"Error inserting ban: {exc}" " (the IPID may not exist)") + raise ServerError(f"Error inserting ban: {exc} (the IPID may not exist)") elif ban_type == "hdid": try: conn.execute( @@ -417,7 +417,7 @@ def log_area(self, event_subtype, client, area, message=None, target=None): def log_connect(self, client, failed=False): """Log a connect attempt.""" logger.info( - f"{client.ipid} (HDID: {client.hdid}) " + f'{"was blocked from connecting" if failed else "connected"}.' + f"{client.ipid} (HDID: {client.hdid}) " + f"{'was blocked from connecting' if failed else 'connected'}." ) with self.db as conn: conn.execute( diff --git a/server/emotes.py b/server/emotes.py index 5a99eb52..c7d6811a 100644 --- a/server/emotes.py +++ b/server/emotes.py @@ -55,20 +55,20 @@ def read_ini(self): self.emotes.add((preanim.lower(), anim.lower(), sfx.lower())) except KeyError as e: logger.warning( - "Broken key %s in character file %s. " "This indicates a malformed character INI file.", + "Broken key %s in character file %s. This indicates a malformed character INI file.", e.args[0], char_path, ) except KeyError as e: logger.warning( - "Unknown key %s in character file %s. " "This indicates a malformed character INI file.", + "Unknown key %s in character file %s. This indicates a malformed character INI file.", e.args[0], char_path, ) return except ValueError as e: logger.warning( - "Value error in character file %s:\n%ss\n" "This indicates a malformed character INI file.", + "Value error in character file %s:\n%ss\nThis indicates a malformed character INI file.", char_path, e, ) diff --git a/server/evidence.py b/server/evidence.py index 0e027b3d..f6d1113c 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -244,7 +244,7 @@ def evidence_swap(self, client, id1, id2): client.area.send_owner_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} swapped evidence {id1+1}: {self.evidences[id1].name} with {id2+1}: {self.evidences[id2].name} in area [{client.area.id}] {client.area.name}.", + f"[{client.id}] {client.showname} swapped evidence {id1 + 1}: {self.evidences[id1].name} with {id2 + 1}: {self.evidences[id2].name} in area [{client.area.id}] {client.area.name}.", "1", ) # send_owner_command does not tell CMs present in the area about evidence manipulation, so let's do that manually @@ -253,7 +253,7 @@ def evidence_swap(self, client, id1, id2): c.send_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} swapped evidence {id1+1}: {self.evidences[id1].name} with {id2+1}: {self.evidences[id2].name} in this area.", + f"[{client.id}] {client.showname} swapped evidence {id1 + 1}: {self.evidences[id1].name} with {id2 + 1}: {self.evidences[id2].name} in this area.", "1", ) @@ -347,7 +347,7 @@ def del_evidence(self, client, id): client.area.send_owner_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} {word} evidence {id+1}: {evi.name} in area [{client.area.id}] {client.area.name}.", + f"[{client.id}] {client.showname} {word} evidence {id + 1}: {evi.name} in area [{client.area.id}] {client.area.name}.", "1", ) # send_owner_command does not tell CMs present in the area about evidence manipulation, so let's do that manually @@ -356,7 +356,7 @@ def del_evidence(self, client, id): c.send_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} {word} evidence {id+1}: {evi.name} in this area.", + f"[{client.id}] {client.showname} {word} evidence {id + 1}: {evi.name} in this area.", "1", ) @@ -440,7 +440,7 @@ def edit_evidence(self, client, id, arg): client.area.send_owner_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} edited evidence {id+1}: {namechange} in area [{client.area.id}] {client.area.name}.", + f"[{client.id}] {client.showname} edited evidence {id + 1}: {namechange} in area [{client.area.id}] {client.area.name}.", "1", ) # send_owner_command does not tell CMs present in the area about evidence manipulation, so let's do that manually @@ -449,6 +449,6 @@ def edit_evidence(self, client, id, arg): c.send_command( "CT", client.server.config["hostname"], - f"[{client.id}] {client.showname} edited evidence {id+1}: {namechange} in this area.", + f"[{client.id}] {client.showname} edited evidence {id + 1}: {namechange} in this area.", "1", ) diff --git a/server/network/aoprotocol.py b/server/network/aoprotocol.py index 30508988..16134e73 100644 --- a/server/network/aoprotocol.py +++ b/server/network/aoprotocol.py @@ -71,8 +71,7 @@ def data_received(self, data): cmd, *args = msg.split("#") self.net_cmd_dispatcher[cmd](self, args) except KeyError: - logger.debug( - "Unknown incoming message from %s: %s", ipid, msg) + logger.debug("Unknown incoming message from %s: %s", ipid, msg) except Exception: print(traceback.format_exc()) self.client.disconnect() @@ -99,9 +98,7 @@ def connection_made(self, transport): # Client needs to send CHECK#% within the timeout - otherwise, # it will be automatically dropped. - self.ping_timeout = asyncio.get_running_loop().call_later( - self.server.config["timeout"], self.client.disconnect - ) + self.ping_timeout = asyncio.get_running_loop().call_later(self.server.config["timeout"], self.client.disconnect) # Disables fantacrypt for clients older than 2.9, required for AO2-Client to send HDID. self.client.send_command("decryptor", "NOENCRYPT") @@ -139,7 +136,12 @@ def validate_net_cmd(self, args, *types, needs_auth=True): :returns: returns True if message was validated """ - if needs_auth and (self.client.char_id is None or self.client.char_id == -1) and not self.client.is_mod and self.client not in self.client.area.owners: + if ( + needs_auth + and (self.client.char_id is None or self.client.char_id == -1) + and not self.client.is_mod + and self.client not in self.client.area.owners + ): return False if len(args) != len(types): return False @@ -165,8 +167,7 @@ def net_cmd_hi(self, args): return # We already got an assigned hdid by the server if self.client.hdid != "": - self.client.send_command( - "KB", "Your HDID was sent a second time by your client.") + self.client.send_command("KB", "Your HDID was sent a second time by your client.") self.client.disconnect() return hdid = self.client.hdid = args[0] @@ -195,12 +196,8 @@ def net_cmd_hi(self, args): self.client.is_checked = True database.log_connect(self.client, failed=False) - self.client.send_command( - "ID", self.client.id, self.server.software, self.server.version - ) - self.client.send_command( - "PN", self.server.player_count, self.server.config["playerlimit"] - ) + self.client.send_command("ID", self.client.id, self.server.software, self.server.version) + self.client.send_command("PN", self.server.player_count, self.server.config["playerlimit"]) def net_cmd_id(self, args): """Client version and PV @@ -209,8 +206,7 @@ def net_cmd_id(self, args): """ # We already got an assigned version by the server if self.client.version != "": - self.client.send_command( - "KB", "Your client version was sent a second time by your client.") + self.client.send_command("KB", "Your client version was sent a second time by your client.") self.client.disconnect() return software, version = args[0], args[1] @@ -222,7 +218,7 @@ def net_cmd_id(self, args): self.client.send_command("FL", *preflist) # Get the list of version vars, making sure the size of the least is at least 3 args - verlist = self.client.version.split('.') + verlist = self.client.version.split(".") # DRO client connected, partial DRO support if self.client.software == "DRO": @@ -249,9 +245,7 @@ def net_cmd_ch(self, _): """ self.client.send_command("CHECK") self.ping_timeout.cancel() - self.ping_timeout = asyncio.get_running_loop().call_later( - self.server.config["timeout"], self.client.disconnect - ) + self.ping_timeout = asyncio.get_running_loop().call_later(self.server.config["timeout"], self.client.disconnect) # Update the timers thru handshake as well to make sure they're always in sync self.client.area.update_timers(self.client, running_only=True) @@ -288,9 +282,7 @@ def net_cmd_rm(self, _): f"🌍[{self.client.area.area_manager.id}] {self.client.area.area_manager.name}\n Double-Click me to see Hubs\n _______" ] else: - song_list = [ - f"🌍[{self.client.area.area_manager.id}] {self.client.area.area_manager.name}" - ] + song_list = [f"🌍[{self.client.area.area_manager.id}] {self.client.area.area_manager.name}"] allowed = self.client.is_mod or self.client in self.client.area.owners area_list = self.client.get_area_list(allowed, allowed) self.client.local_area_list = area_list @@ -336,9 +328,7 @@ def net_cmd_cc(self, args): CC#<client_id:int>#<char_id:int>#<hdid:string>#% """ - if not self.validate_net_cmd( - args, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR, needs_auth=False - ): + if not self.validate_net_cmd(args, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR, needs_auth=False): return elif not self.client.is_checked: return @@ -434,24 +424,24 @@ def net_cmd_ms(self, args): ): # DRO1.1.0 validation monstrosity. ( - msg_type, # 0 - pre, # 1 - folder, # 2 - anim, # 3 - text, # 4 - pos, # 5 - sfx, # 6 - emote_mod, # 7 - cid, # 8 - sfx_delay, # 9 - button, # 10 - evidence, # 11 - flip, # 12 - ding, # 13 - color, # 14 - showname, # 15 - video, # 16 - blankpost, # 17 + msg_type, # 0 + pre, # 1 + folder, # 2 + anim, # 3 + text, # 4 + pos, # 5 + sfx, # 6 + emote_mod, # 7 + cid, # 8 + sfx_delay, # 9 + button, # 10 + evidence, # 11 + flip, # 12 + ding, # 13 + color, # 14 + showname, # 15 + video, # 16 + blankpost, # 17 ) = args if ding != 1: ding = 0 @@ -501,32 +491,32 @@ def net_cmd_ms(self, args): ) = args elif self.validate_net_cmd( args, - self.ArgType.STR, # 0 # msg_type - self.ArgType.STR_OR_EMPTY, # 1 # pre - self.ArgType.STR, # 2 # folder - self.ArgType.STR_OR_EMPTY, # 3 # anim - self.ArgType.STR_OR_EMPTY, # 4 # text - self.ArgType.STR, # 5 # pos - self.ArgType.STR, # 6 # sfx - self.ArgType.INT, # 7 # emote_mod - self.ArgType.INT, # 8 # cid - self.ArgType.INT, # 9 # sfx_delay - self.ArgType.INT_OR_STR, # 10 # button - self.ArgType.INT, # 11 # evidence - self.ArgType.INT, # 12 # flip - self.ArgType.INT, # 13 # ding - self.ArgType.INT, # 14 # color - self.ArgType.STR_OR_EMPTY, # 15 # showname - self.ArgType.STR, # 16 # charid_pair - self.ArgType.STR, # 17 # offset_pair - self.ArgType.INT, # 18 # nonint_pre - self.ArgType.STR, # 19 # sfx_looping - self.ArgType.INT, # 20 # screenshake - self.ArgType.STR, # 21 # frames_shake - self.ArgType.STR, # 22 # frames_realization - self.ArgType.STR, # 23 # frames_sfx - self.ArgType.INT, # 24 # additive - self.ArgType.STR, # 25 # effect + self.ArgType.STR, # 0 # msg_type + self.ArgType.STR_OR_EMPTY, # 1 # pre + self.ArgType.STR, # 2 # folder + self.ArgType.STR_OR_EMPTY, # 3 # anim + self.ArgType.STR_OR_EMPTY, # 4 # text + self.ArgType.STR, # 5 # pos + self.ArgType.STR, # 6 # sfx + self.ArgType.INT, # 7 # emote_mod + self.ArgType.INT, # 8 # cid + self.ArgType.INT, # 9 # sfx_delay + self.ArgType.INT_OR_STR, # 10 # button + self.ArgType.INT, # 11 # evidence + self.ArgType.INT, # 12 # flip + self.ArgType.INT, # 13 # ding + self.ArgType.INT, # 14 # color + self.ArgType.STR_OR_EMPTY, # 15 # showname + self.ArgType.STR, # 16 # charid_pair + self.ArgType.STR, # 17 # offset_pair + self.ArgType.INT, # 18 # nonint_pre + self.ArgType.STR, # 19 # sfx_looping + self.ArgType.INT, # 20 # screenshake + self.ArgType.STR, # 21 # frames_shake + self.ArgType.STR, # 22 # frames_realization + self.ArgType.STR, # 23 # frames_sfx + self.ArgType.INT, # 24 # additive + self.ArgType.STR, # 25 # effect ): # 2.8 validation monstrosity. (rip 2.7) ( @@ -563,38 +553,37 @@ def net_cmd_ms(self, args): if len(pair_args) > 1: pair_order = pair_args[1] except ValueError: - self.client.send_ooc( - "Something went wrong! Please report the issue to the developers.") + self.client.send_ooc("Something went wrong! Please report the issue to the developers.") return elif self.validate_net_cmd( args, - self.ArgType.STR, # 0 # msg_type - self.ArgType.STR_OR_EMPTY, # 1 # pre - self.ArgType.STR, # 2 # folder - self.ArgType.STR_OR_EMPTY, # 3 # anim - self.ArgType.STR_OR_EMPTY, # 4 # text - self.ArgType.STR, # 5 # pos - self.ArgType.STR, # 6 # sfx - self.ArgType.INT, # 7 # emote_mod - self.ArgType.INT, # 8 # cid - self.ArgType.INT, # 9 # sfx_delay - self.ArgType.INT_OR_STR, # 10 # button - self.ArgType.INT, # 11 # evidence - self.ArgType.INT, # 12 # flip - self.ArgType.INT, # 13 # ding - self.ArgType.INT, # 14 # color - self.ArgType.STR_OR_EMPTY, # 15 # showname - self.ArgType.STR, # 16 # charid_pair - self.ArgType.STR, # 17 # offset_pair - self.ArgType.INT, # 18 # nonint_pre - self.ArgType.STR, # 19 # sfx_looping - self.ArgType.INT, # 20 # screenshake - self.ArgType.STR, # 21 # frames_shake - self.ArgType.STR, # 22 # frames_realization - self.ArgType.STR, # 23 # frames_sfx - self.ArgType.INT, # 24 # additive - self.ArgType.STR, # 25 # effect - self.ArgType.INT, # 26 # third_charid + self.ArgType.STR, # 0 # msg_type + self.ArgType.STR_OR_EMPTY, # 1 # pre + self.ArgType.STR, # 2 # folder + self.ArgType.STR_OR_EMPTY, # 3 # anim + self.ArgType.STR_OR_EMPTY, # 4 # text + self.ArgType.STR, # 5 # pos + self.ArgType.STR, # 6 # sfx + self.ArgType.INT, # 7 # emote_mod + self.ArgType.INT, # 8 # cid + self.ArgType.INT, # 9 # sfx_delay + self.ArgType.INT_OR_STR, # 10 # button + self.ArgType.INT, # 11 # evidence + self.ArgType.INT, # 12 # flip + self.ArgType.INT, # 13 # ding + self.ArgType.INT, # 14 # color + self.ArgType.STR_OR_EMPTY, # 15 # showname + self.ArgType.STR, # 16 # charid_pair + self.ArgType.STR, # 17 # offset_pair + self.ArgType.INT, # 18 # nonint_pre + self.ArgType.STR, # 19 # sfx_looping + self.ArgType.INT, # 20 # screenshake + self.ArgType.STR, # 21 # frames_shake + self.ArgType.STR, # 22 # frames_realization + self.ArgType.STR, # 23 # frames_sfx + self.ArgType.INT, # 24 # additive + self.ArgType.STR, # 25 # effect + self.ArgType.INT, # 26 # third_charid ): # AO Golden validation monstrosity ( @@ -632,39 +621,38 @@ def net_cmd_ms(self, args): if len(pair_args) > 1: pair_order = pair_args[1] except ValueError: - self.client.send_ooc( - "Something went wrong! Please report the issue to the developers.") + self.client.send_ooc("Something went wrong! Please report the issue to the developers.") return elif self.validate_net_cmd( args, - self.ArgType.STR, # 0 # msg_type - self.ArgType.STR_OR_EMPTY, # 1 # pre - self.ArgType.STR, # 2 # folder - self.ArgType.STR_OR_EMPTY, # 3 # anim - self.ArgType.STR_OR_EMPTY, # 4 # text - self.ArgType.STR, # 5 # pos - self.ArgType.STR, # 6 # sfx - self.ArgType.INT, # 7 # emote_mod - self.ArgType.INT, # 8 # cid - self.ArgType.INT, # 9 # sfx_delay - self.ArgType.INT_OR_STR, # 10 # button - self.ArgType.INT, # 11 # evidence - self.ArgType.INT, # 12 # flip - self.ArgType.INT, # 13 # ding - self.ArgType.INT, # 14 # color - self.ArgType.STR_OR_EMPTY, # 15 # showname - self.ArgType.STR, # 16 # charid_pair - self.ArgType.STR, # 17 # offset_pair - self.ArgType.INT, # 18 # nonint_pre - self.ArgType.STR, # 19 # sfx_looping - self.ArgType.INT, # 20 # screenshake - self.ArgType.STR, # 21 # frames_shake - self.ArgType.STR, # 22 # frames_realization - self.ArgType.STR, # 23 # frames_sfx - self.ArgType.INT, # 24 # additive - self.ArgType.STR, # 25 # effect - self.ArgType.INT, # 26 # third_charid - self.ArgType.STR_OR_EMPTY, # 27 # video + self.ArgType.STR, # 0 # msg_type + self.ArgType.STR_OR_EMPTY, # 1 # pre + self.ArgType.STR, # 2 # folder + self.ArgType.STR_OR_EMPTY, # 3 # anim + self.ArgType.STR_OR_EMPTY, # 4 # text + self.ArgType.STR, # 5 # pos + self.ArgType.STR, # 6 # sfx + self.ArgType.INT, # 7 # emote_mod + self.ArgType.INT, # 8 # cid + self.ArgType.INT, # 9 # sfx_delay + self.ArgType.INT_OR_STR, # 10 # button + self.ArgType.INT, # 11 # evidence + self.ArgType.INT, # 12 # flip + self.ArgType.INT, # 13 # ding + self.ArgType.INT, # 14 # color + self.ArgType.STR_OR_EMPTY, # 15 # showname + self.ArgType.STR, # 16 # charid_pair + self.ArgType.STR, # 17 # offset_pair + self.ArgType.INT, # 18 # nonint_pre + self.ArgType.STR, # 19 # sfx_looping + self.ArgType.INT, # 20 # screenshake + self.ArgType.STR, # 21 # frames_shake + self.ArgType.STR, # 22 # frames_realization + self.ArgType.STR, # 23 # frames_sfx + self.ArgType.INT, # 24 # additive + self.ArgType.STR, # 25 # effect + self.ArgType.INT, # 26 # third_charid + self.ArgType.STR_OR_EMPTY, # 27 # video ): # KFO Client validation monstrosity ( @@ -703,12 +691,10 @@ def net_cmd_ms(self, args): if len(pair_args) > 1: pair_order = pair_args[1] except ValueError: - self.client.send_ooc( - "Something went wrong! Please report the issue to the developers.") + self.client.send_ooc("Something went wrong! Please report the issue to the developers.") return else: - self.client.send_ooc( - f"Something went wrong! Please report this to the developers:\n{args}") + self.client.send_ooc(f"Something went wrong! Please report this to the developers:\n{args}") return # Targets for whispering whisper_clients = None @@ -718,9 +704,7 @@ def net_cmd_ms(self, args): target_area = self.client.broadcast_list.copy() if self.client.area.cannot_ic_interact(self.client, button): - self.client.send_ooc( - "This is a muted area - ask the CM to be included in the invite list." - ) + self.client.send_ooc("This is a muted area - ask the CM to be included in the invite list.") return False if button == "0" and not self.client.area.can_send_message(self.client): return @@ -731,8 +715,7 @@ def net_cmd_ms(self, args): and not self.client.is_mod and self.client not in self.client.area.owners ): - self.client.send_ooc( - "Showname changes are forbidden in this area!") + self.client.send_ooc("Showname changes are forbidden in this area!") return if self.client.area.is_iniswap(self.client, pre, anim, folder, sfx): folder = self.client.char_name @@ -741,10 +724,9 @@ def net_cmd_ms(self, args): ) return if len(self.client.charcurse) > 0 and folder != self.client.char_name: - self.client.send_ooc( - "You may not iniswap while you are charcursed!") + self.client.send_ooc("You may not iniswap while you are charcursed!") return - if (self.server.config["block_relative"]): + if self.server.config["block_relative"]: pre = derelative(pre) anim = derelative(anim) folder = derelative(folder) @@ -764,15 +746,11 @@ def net_cmd_ms(self, args): and not text.startswith(">") and not text.startswith("=") ): - self.client.send_ooc( - "Blankposting is forbidden in this area!" - ) + self.client.send_ooc("Blankposting is forbidden in this area!") return elif self.client.area.blankposting_forced: if text.strip() != "": - self.client.send_ooc( - "You can only blankpost in this area!" - ) + self.client.send_ooc("You can only blankpost in this area!") return if text.replace(" ", "").startswith("(("): @@ -781,14 +759,8 @@ def net_cmd_ms(self, args): ) return # Scrub text and showname for bad words - if ( - self.client.area.area_manager.censor_ic - and self.server.censors is not None - and len(self.server.censors) > 0 - ): - text = censor( - text, self.server.censors["whole"], self.server.censors["replace"], True - ) + if self.client.area.area_manager.censor_ic and self.server.censors is not None and len(self.server.censors) > 0: + text = censor(text, self.server.censors["whole"], self.server.censors["replace"], True) text = censor( text, self.server.censors["partial"], @@ -835,8 +807,7 @@ def net_cmd_ms(self, args): return text = " ".join(part) except (ValueError, AreaError): - self.client.send_ooc( - "That does not look like a valid area ID!") + self.client.send_ooc("That does not look like a valid area ID!") return if len(self.client.area.testimony) > 0 and ( text.lstrip().startswith(">") or text.lstrip().startswith("<") or text.lstrip().startswith("=") @@ -864,9 +835,7 @@ def net_cmd_ms(self, args): idx = idx % len(self.client.area.testimony) try: self.client.area.testimony_send(idx) - self.client.area.broadcast_ooc( - f"{self.client.showname} has moved to Statement {idx+1}." - ) + self.client.area.broadcast_ooc(f"{self.client.showname} has moved to Statement {idx + 1}.") except Exception: self.client.send_ooc("Invalid index!") return @@ -912,14 +881,10 @@ def net_cmd_ms(self, args): self.client.send_ooc("Your IC showname is way too long!") return if not self.client.is_mod and showname.lstrip().lower().startswith("[m"): - self.client.send_ooc( - "Nice try! You may not spoof [M] tag in your showname." - ) + self.client.send_ooc("Nice try! You may not spoof [M] tag in your showname.") return if (nonint_pre == 1 and button in range(1, 4)) or ( - self.client.area.non_int_pres_only - and not self.client.is_mod - and self.client not in self.client.area.owners + self.client.area.non_int_pres_only and not self.client.is_mod and self.client not in self.client.area.owners ): if emote_mod == 1 or emote_mod == 2: emote_mod = 0 @@ -961,9 +926,7 @@ def net_cmd_ms(self, args): and cid == self.client.area.last_ic_message[8] and text == self.client.area.last_ic_message[4] ): - self.client.send_ooc( - "Your message is a repeat of the last one, don't spam!" - ) + self.client.send_ooc("Your message is a repeat of the last one, don't spam!") return # We are blankposting. @@ -1013,9 +976,7 @@ def net_cmd_ms(self, args): clients = ",".join(clients) else: whisper_clients = [ - c - for c in self.client.area.clients - if c.pos == self.client.pos and not c == self.client + c for c in self.client.area.clients if c.pos == self.client.pos and not c == self.client ] clients = "" text = " ".join(part) @@ -1049,22 +1010,20 @@ def net_cmd_ms(self, args): evidence = self.client.evi_list[evidence] evi = area.evi_list.evidences[evidence - 1] self.client.area.broadcast_ooc( - f"[{self.client.id}] {self.client.showname} has presented evidence: {evi.name}.") + f"[{self.client.id}] {self.client.showname} has presented evidence: {evi.name}." + ) if evi.hiding_client is not None: c = evi.hiding_client c.hide(False) c.area.broadcast_area_list(c) - self.client.send_ooc( - f"You discover {c.showname} in the {evi.name}!") + self.client.send_ooc(f"You discover {c.showname} in the {evi.name}!") if area.present_reveals_evidence and evi.pos != "all": evi.desc = f"(👀Discovered in pos: {evi.pos})\n{evi.desc}" evi.pos = "all" area.broadcast_evidence_list() - asyncio.get_running_loop().call_soon( - evi.trigger, area, "present", self.client - ) + asyncio.get_running_loop().call_soon(evi.trigger, area, "present", self.client) # target_area.trigger('present') except IndexError: evidence = 0 @@ -1073,7 +1032,7 @@ def net_cmd_ms(self, args): if self.client.used_showname_command: showname = self.client.showname self.client.showname = showname - + # Here, we check the pair stuff, and save info about it to the client. # Notably, while we only get a charid_pair and an offset, we send back a chair_pair, an emote, a talker offset # and an other offset. @@ -1106,10 +1065,7 @@ def net_cmd_ms(self, args): if ( not confirmed and target.char_id == self.client.charid_pair - and ( - target.charid_pair == self.client.char_id - or target.third_charid == self.client.char_id - ) + and (target.charid_pair == self.client.char_id or target.third_charid == self.client.char_id) and target != self.client and target.pos == self.client.pos ): @@ -1131,10 +1087,7 @@ def net_cmd_ms(self, args): if ( not third_confirmed and target.char_id == self.client.third_charid - and ( - target.charid_pair == self.client.char_id - or target.third_charid == self.client.char_id - ) + and (target.charid_pair == self.client.char_id or target.third_charid == self.client.char_id) and target != self.client and target.pos == self.client.pos and self.client.charid_pair != self.client.third_charid @@ -1154,17 +1107,16 @@ def net_cmd_ms(self, args): clients_pos.sort(key=lambda x: x.id) if len(clients_pos) >= 3 and self.client.area.auto_pair_max == "triple": position = clients_pos.index(self.client) - if len(clients_pos) >= position+2: + if len(clients_pos) >= position + 2: if position > 0: - client_pair = clients_pos[position-1] - third_client = clients_pos[position+1] + client_pair = clients_pos[position - 1] + third_client = clients_pos[position + 1] else: - client_pair = clients_pos[position+1] - third_client = clients_pos[position+2] + client_pair = clients_pos[position + 1] + third_client = clients_pos[position + 2] else: - client_pair = clients_pos[position-1] - third_client = clients_pos[position-2] - + client_pair = clients_pos[position - 1] + third_client = clients_pos[position - 2] charid_pair = f"{client_pair.char_id}^0" other_emote = client_pair.last_sprite @@ -1194,7 +1146,7 @@ def net_cmd_ms(self, args): self.client.last_offset = 0 client_pair.last_offset = -33 third_client.last_offset = 33 - + else: offset_pair = 0 if len(clients_pos) >= 2: @@ -1215,12 +1167,12 @@ def net_cmd_ms(self, args): other_flip = client_pair.flip other_folder = client_pair.claimed_folder - ver = self.client.version.split('.') + ver = self.client.version.split(".") if len(ver) >= 2: # Client versions 2.9 or less need to get their SFX corrected due to 2.10 changes if ver[0].isnumeric() and int(ver[0]) <= 2 and ver[1].isnumeric() and int(ver[1]) <= 9: if emote_mod not in (1, 6): - sfx = '' + sfx = "" if whisper_clients is not None: whisper_clients.insert(0, self.client) @@ -1240,18 +1192,13 @@ def net_cmd_ms(self, args): if additive and ( a.last_ic_message is None or cid != a.last_ic_message[8] - or ( - a.last_ic_message[4].strip() == "" - and a.last_ic_message[28] != 1 - ) + or (a.last_ic_message[4].strip() == "" and a.last_ic_message[28] != 1) ): additive = 0 if len(a.pos_lock) > 0: tempos = a.pos_lock[0] if a.last_ic_message is not None and ( - anim == "" or - len(a.pos_lock) <= 0 - or a.last_ic_message[5] not in a.pos_lock + anim == "" or len(a.pos_lock) <= 0 or a.last_ic_message[5] not in a.pos_lock ): # Use the same pos tempos = a.last_ic_message[5] @@ -1259,42 +1206,42 @@ def net_cmd_ms(self, args): tempdeskmod = a.last_ic_message[0] a.send_command( "MS", - tempdeskmod, # 0 - pre, # 1 - folder, # 2 - anim, # 3 - msg, # 4 - tempos, # 5 - sfx, # 6 - emote_mod, # 7 - cid, # 8 - sfx_delay, # 9 - button, # 10 - self.client.evi_list[evidence], # 11 - flip, # 12 - ding, # 13 - color, # 14 - showname, # 15 - charid_pair, # 16 - other_folder, # 17 - other_emote, # 18 - offset_pair, # 19 - other_offset, # 20 - other_flip, # 21 - nonint_pre, # 22 - sfx_looping, # 23 - screenshake, # 24 - frames_shake, # 25 - frames_realization, # 26 - frames_sfx, # 27 - add, # 28 - effect, # 29 - third_charid, # 30 - third_folder, # 31 - third_emote, # 32 - third_offset, # 33 - third_flip, # 33 - video, # 34 + tempdeskmod, # 0 + pre, # 1 + folder, # 2 + anim, # 3 + msg, # 4 + tempos, # 5 + sfx, # 6 + emote_mod, # 7 + cid, # 8 + sfx_delay, # 9 + button, # 10 + self.client.evi_list[evidence], # 11 + flip, # 12 + ding, # 13 + color, # 14 + showname, # 15 + charid_pair, # 16 + other_folder, # 17 + other_emote, # 18 + offset_pair, # 19 + other_offset, # 20 + other_flip, # 21 + nonint_pre, # 22 + sfx_looping, # 23 + screenshake, # 24 + frames_shake, # 25 + frames_realization, # 26 + frames_sfx, # 27 + add, # 28 + effect, # 29 + third_charid, # 30 + third_folder, # 31 + third_emote, # 32 + third_offset, # 33 + third_flip, # 33 + video, # 34 ) a_list = ", ".join([str(a.id) for a in target_area]) if self.client.area not in target_area: @@ -1350,8 +1297,7 @@ def net_cmd_ms(self, args): if whisper_clients is None: # Enforce the area msg delay delay = self.client.area.parse_msg_delay(msg) - self.client.area.next_message_time = round( - time.time() * 1000.0 + delay) + self.client.area.next_message_time = round(time.time() * 1000.0 + delay) if ( text.strip() != "" or self.client.area.last_ic_message is None @@ -1381,9 +1327,7 @@ def net_cmd_ms(self, args): .replace("\\f", "") ) # escape chars - txt = txt.replace( - "@", "@\u200b" - ) # The only way to escape a Discord ping is a zero width space... + txt = txt.replace("@", "@\u200b") # The only way to escape a Discord ping is a zero width space... txt = txt.replace("<num>", "\\#") txt = txt.replace("<and>", "&") txt = txt.replace("<percent>", "%") @@ -1394,15 +1338,19 @@ def net_cmd_ms(self, args): if not txt.strip(): # Discord blankpost txt = "_ _" - self.server.bridgebot.queue_message( - webname, txt, self.client.char_name, anim - ) + self.server.bridgebot.queue_message(webname, txt, self.client.char_name, anim) # Check if the message can be considered to contain actions in it if text.lstrip().startswith("*") or "[" in text or "|" in text or color == 3: is_action = True # * at the end is "correction", don't count it as action - if "[" not in text and "|" not in text and color != 3 and text.count("*") == 1 and text.rstrip().endswith("*"): + if ( + "[" not in text + and "|" not in text + and color != 3 + and text.count("*") == 1 + and text.rstrip().endswith("*") + ): is_action = False if is_action: @@ -1429,11 +1377,7 @@ def net_cmd_ms(self, args): ] # Update the emote variable with what we found inside the parentheses else: # If we swap emotes after a full stop, we add the separator variable (\p\p\p) to make it less abrupt - text = ( - stripped_message + separator - if stripped_message.endswith(".") - else stripped_message + " " - ) + text = stripped_message + separator if stripped_message.endswith(".") else stripped_message + " " emote_value = ( anim if index == 0 else emote ) # Use 'anim' if it's the first message, otherwise use the emote variable @@ -1488,10 +1432,7 @@ def net_cmd_ms(self, args): if additive and ( self.client.area.last_ic_message is None or cid != self.client.area.last_ic_message[8] - or ( - self.client.area.last_ic_message[4].strip() == "" - and self.client.area.last_ic_message[28] != 1 - ) + or (self.client.area.last_ic_message[4].strip() == "" and self.client.area.last_ic_message[28] != 1) ): additive = 0 @@ -1580,7 +1521,7 @@ def net_cmd_ms(self, args): if self.client.software == "DRO": # send it back to the client self.client.send_command("ackMS") - + # Broadcast our presence if our showname changed or if we spoke while sneaking if old_showname != self.client.showname or self.client.sneaking: self.client.area.broadcast_player_list() @@ -1594,24 +1535,17 @@ def net_cmd_ct(self, args): if not self.client.is_checked: return - if ( - self.client.is_ooc_muted - ): # Checks to see if the client has been muted by a mod + if self.client.is_ooc_muted: # Checks to see if the client has been muted by a mod self.client.send_ooc("You are muted by a moderator.") return - if not self.validate_net_cmd( - args, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, needs_auth=False - ): + if not self.validate_net_cmd(args, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, needs_auth=False): return args[0] = args[0].strip() if args[0] == "": - self.client.send_ooc( - "You must insert your OOC Name into 'Name' before you can speak.") + self.client.send_ooc("You must insert your OOC Name into 'Name' before you can speak.") return if len(args[0]) > 30: - self.client.send_ooc( - "Your OOC name is too long! Limit it to 30 characters." - ) + self.client.send_ooc("Your OOC name is too long! Limit it to 30 characters.") return if self.client.ooc_mute(): self.client.send_ooc( @@ -1620,8 +1554,7 @@ def net_cmd_ct(self, args): return for c in args[0]: if unicodedata.category(c) == "Cf": - self.client.send_ooc( - "You cannot use format characters in your name!") + self.client.send_ooc("You cannot use format characters in your name!") return if ( args[0].startswith(self.server.config["hostname"]) @@ -1666,19 +1599,14 @@ def net_cmd_ct(self, args): ) if not self.client.is_valid_name(args[0]): - self.client.send_ooc( - "Your OOC name is invalid!" - ) + self.client.send_ooc("Your OOC name is invalid!") return self.client.name = args[0] if args[1].lstrip() != args[1] and args[1].lstrip().startswith("/"): - self.client.send_ooc( - "Your message was not sent for safety reasons: you left space before that slash." - ) + self.client.send_ooc("Your message was not sent for safety reasons: you left space before that slash.") return - database.log_area("chat.ooc", self.client, - self.client.area, message=args[1]) + database.log_area("chat.ooc", self.client, self.client.area, message=args[1]) if args[1].startswith("/"): spl = args[1][1:].split(" ", 1) cmd = spl[0].lower() @@ -1720,9 +1648,7 @@ def net_cmd_ct(self, args): if self.client.disemvowel: args[1] = self.client.disemvowel_message(args[1]) self.client.area.send_command("CT", name, args[1]) - self.client.area.send_owner_command( - "CT", f"[{self.client.area.id}]{name}", args[1] - ) + self.client.area.send_owner_command("CT", f"[{self.client.area.id}]{name}", args[1]) def net_cmd_mc(self, args): """Play music. @@ -1732,7 +1658,7 @@ def net_cmd_mc(self, args): """ if not self.client.is_checked: return - + if len(args) <= 0: return # Test for Hub or Area Switcher @@ -1753,10 +1679,7 @@ def net_cmd_mc(self, args): "FA", *[ "🌐 Hubs 🌐\n Double-Click me to see Areas\n _______", - *[ - f"[{hub.id}] {hub.name} (users: {hub.count})" - for hub in self.client.server.hub_manager.hubs - ], + *[f"[{hub.id}] {hub.name} (users: {hub.count})" for hub in self.client.server.hub_manager.hubs], ], ) return @@ -1777,13 +1700,10 @@ def net_cmd_mc(self, args): if self.client.viewing_hub_list: called_function = "ooc_cmd_hub" # We can get cheeky and spoof ARUP info with normal song names - getattr(commands, called_function)( - self.client, args[0].split("\n")[0]) + getattr(commands, called_function)(self.client, args[0].split("\n")[0]) except (IndexError, AreaError): if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): - if not self.validate_net_cmd( - args, self.ArgType.STR, self.ArgType.INT, self.ArgType.STR_OR_EMPTY - ): + if not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT, self.ArgType.STR_OR_EMPTY): if not self.validate_net_cmd( args, self.ArgType.STR, @@ -1808,27 +1728,17 @@ def net_cmd_rt(self, args): self.client.send_ooc("You are muted by a moderator.") return if not self.client.can_wtce: - self.client.send_ooc( - "You were blocked from using judge signs by a moderator." - ) + self.client.send_ooc("You were blocked from using judge signs by a moderator.") return - if ( - not self.client.area.can_wtce - and not self.client.is_mod - and self.client not in self.client.area.owners - ): - self.client.send_ooc( - "Only CMs and mods may use judge buttons in this area!" - ) + if not self.client.area.can_wtce and not self.client.is_mod and self.client not in self.client.area.owners: + self.client.send_ooc("Only CMs and mods may use judge buttons in this area!") return if self.client.area.cannot_ic_interact(self.client): - self.client.send_ooc( - "You are not on the area's invite list, and thus, you cannot use the WTCE buttons!" - ) + self.client.send_ooc("You are not on the area's invite list, and thus, you cannot use the WTCE buttons!") return - if not self.validate_net_cmd( - args, self.ArgType.STR - ) and not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): + if not self.validate_net_cmd(args, self.ArgType.STR) and not self.validate_net_cmd( + args, self.ArgType.STR, self.ArgType.INT + ): return if args[0] == "testimony1": sign = "WT" @@ -1846,13 +1756,10 @@ def net_cmd_rt(self, args): if len(self.client.broadcast_list) > 0: try: - a_list = ", ".join([str(a.id) - for a in self.client.broadcast_list]) + a_list = ", ".join([str(a.id) for a in self.client.broadcast_list]) self.client.send_ooc(f"Broadcasting to areas {a_list}") if len(args) == 1: - self.client.area.area_manager.send_remote_command( - self.client.broadcast_list, "RT", args[0] - ) + self.client.area.area_manager.send_remote_command(self.client.broadcast_list, "RT", args[0]) elif len(args) == 2: self.client.area.area_manager.send_remote_command( self.client.broadcast_list, "RT", args[0], args[1] @@ -1873,13 +1780,7 @@ def net_cmd_rt(self, args): if self.client in self.client.area.owners: if self.client.area.last_ic_message is not None and sign == "WT": # remove centering chars and strip space chars as well as any coloring - msg = ( - self.client.area.last_ic_message[4] - .replace("~", "") - .replace("|", "") - .replace("`", "") - .strip() - ) + msg = self.client.area.last_ic_message[4].replace("~", "").replace("|", "").replace("`", "").strip() msg = msg.replace("-", "") msg = msg.replace("=", "") msg = msg.strip() @@ -1896,8 +1797,7 @@ def net_cmd_rt(self, args): if sign == "CE": if self.client.area.recording: self.client.area.recording = False - self.client.area.broadcast_ooc( - "Testimony recording stopped!") + self.client.area.broadcast_ooc("Testimony recording stopped!") # Display the testimony title if len(self.client.area.testimony) > 0: statement = self.client.area.testimony[0] @@ -1905,8 +1805,7 @@ def net_cmd_rt(self, args): # See if the testimony is supposed to end here. # Center it and make it speedy - lst[4] = "~~}}-- " + \ - self.client.area.testimony_title + " --" + lst[4] = "~~}}-- " + self.client.area.testimony_title + " --" # Make it orange lst[14] = 3 @@ -1947,9 +1846,7 @@ def net_cmd_casea(self, args): return if self.client in self.client.area.owners: if not self.client.can_call_case(): - self.client.send_ooc( - "Please wait 60 seconds between case announcements!" - ) + self.client.send_ooc("Please wait 60 seconds between case announcements!") return if ( @@ -1959,9 +1856,7 @@ def net_cmd_casea(self, args): and not args[4] == "1" and not args[5] == "1" ): - self.client.send_ooc( - "You should probably announce the case to at least one person." - ) + self.client.send_ooc("You should probably announce the case to at least one person.") return msg = "=== Case Announcement ===\r\n{} [{}] is hosting {}, looking for ".format( self.client.showname, self.client.id, args[0] @@ -1978,22 +1873,14 @@ def net_cmd_casea(self, args): msg += ", ".join(lookingfor) + ".\r\n==================" - self.client.server.send_all_cmd_pred( - "CASEA", msg, args[1], args[2], args[3], args[4], args[5], "1" - ) + self.client.server.send_all_cmd_pred("CASEA", msg, args[1], args[2], args[3], args[4], args[5], "1") self.client.set_case_call_delay() - log_data = { - k: v - for k, v in zip(("message", "def", "pro", "jud", "jur", "steno"), args) - } - database.log_area("case", self.client, - self.client.area, message=log_data) + log_data = {k: v for k, v in zip(("message", "def", "pro", "jud", "jur", "steno"), args)} + database.log_area("case", self.client, self.client.area, message=log_data) else: - self.client.send_ooc( - "You cannot announce a case in an area where you are not a CM!" - ) + self.client.send_ooc("You cannot announce a case in an area where you are not a CM!") def net_cmd_hp(self, args): """Sets the penalty bar. @@ -2015,8 +1902,7 @@ def net_cmd_hp(self, args): return try: self.client.area.change_hp(args[0], args[1]) - self.client.area.add_to_judgelog( - self.client, "changed the penalties") + self.client.area.add_to_judgelog(self.client, "changed the penalties") database.log_area("hp", self.client, self.client.area) except AreaError: return @@ -2041,9 +1927,7 @@ def net_cmd_pe(self, args): if len(args) < 3: return # evi = Evidence(args[0], args[1], args[2], self.client.pos) - self.client.area.evi_list.add_evidence( - self.client, args[0], args[1], args[2] - ) + self.client.area.evi_list.add_evidence(self.client, args[0], args[1], args[2]) database.log_area("evidence.add", self.client, self.client.area) self.client.area.broadcast_evidence_list() @@ -2103,9 +1987,7 @@ def net_cmd_zz(self, args): if len(args) < 1: self.client.set_mod_call_delay() database.log_area("modcall", self.client, self.client.area) - self.server.webhooks.modcall( - char=self.client.char_name, ipid=self.client.ip, area=self.client.area - ) + self.server.webhooks.modcall(char=self.client.char_name, ipid=self.client.ip, area=self.client.area) self.server.send_all_cmd_pred( "ZZ", "[{} UTC] {} ({}) in hub {} [{}]{} without reason (not using 2.6?)".format( @@ -2120,8 +2002,7 @@ def net_cmd_zz(self, args): ) else: self.client.set_mod_call_delay() - database.log_area("modcall", self.client, - self.client.area, message=args[0]) + database.log_area("modcall", self.client, self.client.area, message=args[0]) self.server.webhooks.modcall( char=self.client.char_name, ipid=self.client.ip, @@ -2161,10 +2042,10 @@ def net_cmd_tt(self, args): Sended when the client is typing on the IC chat. TT#<state: int>#<char_name:str>#<emote_name:str>#% - + state: 0 = stopped typing | 1 = typing - + Client implementation details: The state is cleared after the client sends the IC message. Also cleared after 100-200ms of inactivity. @@ -2174,7 +2055,7 @@ def net_cmd_tt(self, args): # char_name if args[1].lower() != self.client.char_name.lower(): - # char_name + # char_name self.client.iniswap = args[1] else: self.client.iniswap = "" @@ -2189,26 +2070,23 @@ def net_cmd_tt(self, args): def net_cmd_cu(self, args): """ - + Sets the character_URL of the client. - + CU#<authority:int>#<action:int>#<char_name:str>#<link:str>#% - - authority: + + authority: 0 = server | 1 = client, - + action: 0 = Delete, | 1 = Add, | 2 = Clear all, - + """ - if not self.validate_net_cmd(args, - self.ArgType.INT, - self.ArgType.INT, - self.ArgType.STR_OR_EMPTY, - self.ArgType.STR, - needs_auth=False): + if not self.validate_net_cmd( + args, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.STR, needs_auth=False + ): return if args[0] == 0: @@ -2230,7 +2108,7 @@ def net_cmd_cu(self, args): if args[2] == "": for c in clients: # authority, action, char_name - c.send_command('CU', args[0], "1", self.client.f_char_name_raw) + c.send_command("CU", args[0], "1", self.client.f_char_name_raw) self.client.char_url = "" return @@ -2239,20 +2117,20 @@ def net_cmd_cu(self, args): for c in clients: # Clear the old char_url # authority, action, char_name - c.send_command('CU', args[0], "0", self.client.f_char_name_raw) + c.send_command("CU", args[0], "0", self.client.f_char_name_raw) # Add the new char_url # authority, action, char_name, link - c.send_command('CU', args[0], args[1], args[2], args[3]) + c.send_command("CU", args[0], args[1], args[2], args[3]) else: for c in clients: # Set the char_url # authority, action, char_name, link - c.send_command('CU', args[0], args[1], args[2], args[3]) + c.send_command("CU", args[0], args[1], args[2], args[3]) # char_name if args[2].lower() != self.client.char_name.lower(): - # char_name + # char_name self.client.iniswap = args[2] else: self.client.iniswap = "" @@ -2260,7 +2138,6 @@ def net_cmd_cu(self, args): # link self.client.char_url = args[3] - net_cmd_dispatcher = { "HI": net_cmd_hi, # handshake "ID": net_cmd_id, # client version